diff --git a/.rubocop_todo/rspec/multiple_memoized_helpers.yml b/.rubocop_todo/rspec/multiple_memoized_helpers.yml index a489264536b..45c35359850 100644 --- a/.rubocop_todo/rspec/multiple_memoized_helpers.yml +++ b/.rubocop_todo/rspec/multiple_memoized_helpers.yml @@ -24,6 +24,7 @@ RSpec/MultipleMemoizedHelpers: - 'spec/requests/api/issues/issues_spec.rb' - 'spec/requests/api/issues/put_projects_issues_spec.rb' - 'spec/requests/api/maven_packages_spec.rb' + - 'spec/requests/api/users_spec.rb' - 'spec/services/boards/issues/list_service_spec.rb' - 'spec/services/labels/promote_service_spec.rb' - 'spec/services/merge_requests/push_options_handler_service_spec.rb' diff --git a/Gemfile b/Gemfile index d8f93f9280b..87e243086df 100644 --- a/Gemfile +++ b/Gemfile @@ -232,7 +232,7 @@ gem 'nokogiri', '~> 1.16' # rubocop:todo Gemfile/MissingFeatureCategory gem 'gitlab-glfm-markdown', '~> 0.0.17', feature_category: :team_planning # Calendar rendering -gem 'icalendar' # rubocop:todo Gemfile/MissingFeatureCategory +gem 'icalendar', '~> 2.10.1', feature_category: :system_access # Diffs gem 'diffy', '~> 3.4' # rubocop:todo Gemfile/MissingFeatureCategory diff --git a/Gemfile.checksum b/Gemfile.checksum index c9962993265..ddbc57254ef 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -317,7 +317,7 @@ {"name":"httpclient","version":"2.8.3","platform":"ruby","checksum":"2951e4991214464c3e92107e46438527d23048e634f3aee91c719e0bdfaebda6"}, {"name":"i18n","version":"1.14.4","platform":"ruby","checksum":"c7deedead0866ea9102975a4eab7968f53de50793a0c211a37808f75dd187551"}, {"name":"i18n_data","version":"0.13.1","platform":"ruby","checksum":"e5aa99b09a69b463bb0443fc1f9540351a49f3d1541c5e91316bafa035c63f66"}, -{"name":"icalendar","version":"2.8.0","platform":"ruby","checksum":"e404f970c7572bdebf6f09f9890970b68aab400ba9e609dc7d46098f28d0ee87"}, +{"name":"icalendar","version":"2.10.1","platform":"ruby","checksum":"1f3108bb95c89e03d418ac95b2fd6182c0b5d112bbe757cf6e23e3282a3f710e"}, {"name":"ice_cube","version":"0.16.4","platform":"ruby","checksum":"da117e5de24bdc33931be629f9b55048641924442c7e9b72fedc05e5592531b7"}, {"name":"ice_nine","version":"0.11.2","platform":"ruby","checksum":"5d506a7d2723d5592dc121b9928e4931742730131f22a1a37649df1c1e2e63db"}, {"name":"imagen","version":"0.1.8","platform":"ruby","checksum":"fde7b727d4fe79c6bb5ac46c1f7184bf87a6d54df54d712ad2be039d2f93a162"}, diff --git a/Gemfile.lock b/Gemfile.lock index aa9a829983a..498ffe8af94 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -955,7 +955,7 @@ GEM i18n (1.14.4) concurrent-ruby (~> 1.0) i18n_data (0.13.1) - icalendar (2.8.0) + icalendar (2.10.1) ice_cube (~> 0.16) ice_cube (0.16.4) ice_nine (0.11.2) @@ -2062,7 +2062,7 @@ DEPENDENCIES html-pipeline (~> 2.14.3) html2text httparty (~> 0.21.0) - icalendar + icalendar (~> 2.10.1) influxdb-client (~> 3.1) invisible_captcha (~> 2.1.0) ipaddr (~> 1.2.5) diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb index 89c1ab6fb27..638b16d65c2 100644 --- a/app/graphql/resolvers/merge_requests_resolver.rb +++ b/app/graphql/resolvers/merge_requests_resolver.rb @@ -90,10 +90,14 @@ module Resolvers required: false, description: 'Merge requests updated before the timestamp.' + argument :label_name, [GraphQL::Types::String, { null: true }], + required: false, + description: 'Labels applied to the merge request.' argument :labels, [GraphQL::Types::String], required: false, as: :label_name, - description: 'Array of label names. All resolved merge requests will have all of these labels.' + description: 'Array of label names. All resolved merge requests will have all of these labels.', + deprecated: { reason: 'Use `labelName`', milestone: '17.1' } argument :merged_after, Types::TimeType, required: false, description: 'Merge requests merged after the date.' diff --git a/app/models/users/credit_card_validation.rb b/app/models/users/credit_card_validation.rb index 14702252203..f12c3767cf6 100644 --- a/app/models/users/credit_card_validation.rb +++ b/app/models/users/credit_card_validation.rb @@ -24,6 +24,10 @@ module Users validates :zuora_payment_method_xid, length: { maximum: 50 }, uniqueness: true, allow_nil: true + validates :stripe_setup_intent_xid, length: { maximum: 255 }, allow_nil: true + validates :stripe_payment_method_xid, length: { maximum: 255 }, allow_nil: true + validates :stripe_card_fingerprint, length: { maximum: 255 }, allow_nil: true + validates :last_digits_hash, length: { maximum: 44 } validates :holder_name_hash, length: { maximum: 44 } validates :expiration_date_hash, length: { maximum: 44 } diff --git a/app/services/users/upsert_credit_card_validation_service.rb b/app/services/users/upsert_credit_card_validation_service.rb index 462cc72b4fc..7e24bfcfee0 100644 --- a/app/services/users/upsert_credit_card_validation_service.rb +++ b/app/services/users/upsert_credit_card_validation_service.rb @@ -17,7 +17,10 @@ module Users holder_name: holder_name, network: network, expiration_date: expiration_date, - zuora_payment_method_xid: zuora_payment_method_xid + zuora_payment_method_xid: zuora_payment_method_xid, + stripe_setup_intent_xid: stripe_setup_intent_xid, + stripe_payment_method_xid: stripe_payment_method_xid, + stripe_card_fingerprint: stripe_card_fingerprint } credit_card.update!(credit_card_params) @@ -56,6 +59,18 @@ module Users params[:zuora_payment_method_xid] end + def stripe_setup_intent_xid + params[:stripe_setup_intent_xid] + end + + def stripe_payment_method_xid + params[:stripe_payment_method_xid] + end + + def stripe_card_fingerprint + params[:stripe_card_fingerprint] + end + def expiration_date year = params.fetch(:credit_card_expiration_year) month = params.fetch(:credit_card_expiration_month) diff --git a/db/docs/batched_background_migrations/backfill_vulnerability_flags_project_id.yml b/db/docs/batched_background_migrations/backfill_vulnerability_flags_project_id.yml new file mode 100644 index 00000000000..1cf26d42e4f --- /dev/null +++ b/db/docs/batched_background_migrations/backfill_vulnerability_flags_project_id.yml @@ -0,0 +1,9 @@ +--- +migration_job_name: BackfillVulnerabilityFlagsProjectId +description: Backfills sharding key `vulnerability_flags.project_id` from `vulnerability_occurrences`. +feature_category: vulnerability_management +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156323 +milestone: '17.2' +queued_migration_version: 20240613154933 +finalize_after: '2024-08-22' +finalized_by: # version of the migration that finalized this BBM diff --git a/db/docs/ci_runner_projects.yml b/db/docs/ci_runner_projects.yml index 44412291ff1..fad9a6b0ecd 100644 --- a/db/docs/ci_runner_projects.yml +++ b/db/docs/ci_runner_projects.yml @@ -8,4 +8,5 @@ description: Relationships between runners and projects for project runners introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/046b28312704f3131e72dcd2dbdacc5264d4aa62 milestone: '8.0' gitlab_schema: gitlab_ci -sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/459996 +sharding_key: + project_id: projects diff --git a/db/docs/vulnerability_flags.yml b/db/docs/vulnerability_flags.yml index 01c6f85a183..bc1a8e4a445 100644 --- a/db/docs/vulnerability_flags.yml +++ b/db/docs/vulnerability_flags.yml @@ -4,7 +4,8 @@ classes: - Vulnerabilities::Flag feature_categories: - vulnerability_management -description: Stores additional information for vulnerabilities, for example if a vulnerability is identified as a false positive +description: Stores additional information for vulnerabilities, for example if a vulnerability + is identified as a false positive introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65573 milestone: '14.1' gitlab_schema: gitlab_main_cell @@ -19,3 +20,4 @@ desired_sharding_key: table: vulnerability_occurrences sharding_key: project_id belongs_to: finding +desired_sharding_key_migration_job_name: BackfillVulnerabilityFlagsProjectId diff --git a/db/migrate/20240613154929_add_project_id_to_vulnerability_flags.rb b/db/migrate/20240613154929_add_project_id_to_vulnerability_flags.rb new file mode 100644 index 00000000000..e058eb53ef2 --- /dev/null +++ b/db/migrate/20240613154929_add_project_id_to_vulnerability_flags.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddProjectIdToVulnerabilityFlags < Gitlab::Database::Migration[2.2] + milestone '17.2' + + def change + add_column :vulnerability_flags, :project_id, :bigint + end +end diff --git a/db/migrate/20240617100206_add_stripe_identifiers_to_credit_card_validation.rb b/db/migrate/20240617100206_add_stripe_identifiers_to_credit_card_validation.rb new file mode 100644 index 00000000000..2ddcc9bd967 --- /dev/null +++ b/db/migrate/20240617100206_add_stripe_identifiers_to_credit_card_validation.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class AddStripeIdentifiersToCreditCardValidation < Gitlab::Database::Migration[2.2] + disable_ddl_transaction! + + milestone '17.2' + + def up + with_lock_retries do + add_column :user_credit_card_validations, :stripe_setup_intent_xid, :text, if_not_exists: true + add_column :user_credit_card_validations, :stripe_payment_method_xid, :text, if_not_exists: true + add_column :user_credit_card_validations, :stripe_card_fingerprint, :text, if_not_exists: true + end + + # Stripe identifiers are currently only around 32 characters, but setting character limit to 255 per the API + # upgrade guidance for compatability with future Stripe-side changes. + # + # More info: https://docs.stripe.com/upgrades + add_text_limit :user_credit_card_validations, :stripe_setup_intent_xid, 255 + add_text_limit :user_credit_card_validations, :stripe_payment_method_xid, 255 + add_text_limit :user_credit_card_validations, :stripe_card_fingerprint, 255 + end + + def down + remove_column :user_credit_card_validations, :stripe_setup_intent_xid, if_exists: true + remove_column :user_credit_card_validations, :stripe_payment_method_xid, if_exists: true + remove_column :user_credit_card_validations, :stripe_card_fingerprint, if_exists: true + end +end diff --git a/db/migrate/20240617101253_add_index_for_stripe_card_fingerprint_to_credit_card_validation.rb b/db/migrate/20240617101253_add_index_for_stripe_card_fingerprint_to_credit_card_validation.rb new file mode 100644 index 00000000000..367e368ca6b --- /dev/null +++ b/db/migrate/20240617101253_add_index_for_stripe_card_fingerprint_to_credit_card_validation.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class AddIndexForStripeCardFingerprintToCreditCardValidation < Gitlab::Database::Migration[2.2] + disable_ddl_transaction! + + milestone '17.2' + + INDEX_NAME = 'index_user_credit_card_validations_on_stripe_card_fingerprint' + + def up + add_concurrent_index :user_credit_card_validations, + :stripe_card_fingerprint, + name: INDEX_NAME + end + + def down + remove_concurrent_index :user_credit_card_validations, :stripe_card_fingerprint, name: INDEX_NAME + end +end diff --git a/db/post_migrate/20240613152414_add_not_null_constraint_to_runner_project_project_id.rb b/db/post_migrate/20240613152414_add_not_null_constraint_to_runner_project_project_id.rb new file mode 100644 index 00000000000..054be590e74 --- /dev/null +++ b/db/post_migrate/20240613152414_add_not_null_constraint_to_runner_project_project_id.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddNotNullConstraintToRunnerProjectProjectId < Gitlab::Database::Migration[2.2] + milestone '17.2' + + disable_ddl_transaction! + + def up + # This will add the `NOT NULL` constraint and validate it + add_not_null_constraint :ci_runner_projects, :project_id + end + + def down + # Down is required as `add_not_null_constraint` is not reversible + remove_not_null_constraint :ci_runner_projects, :project_id + end +end diff --git a/db/post_migrate/20240613154930_index_vulnerability_flags_on_project_id.rb b/db/post_migrate/20240613154930_index_vulnerability_flags_on_project_id.rb new file mode 100644 index 00000000000..1efdd194afa --- /dev/null +++ b/db/post_migrate/20240613154930_index_vulnerability_flags_on_project_id.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class IndexVulnerabilityFlagsOnProjectId < Gitlab::Database::Migration[2.2] + milestone '17.2' + disable_ddl_transaction! + + INDEX_NAME = 'index_vulnerability_flags_on_project_id' + + def up + add_concurrent_index :vulnerability_flags, :project_id, name: INDEX_NAME + end + + def down + remove_concurrent_index_by_name :vulnerability_flags, INDEX_NAME + end +end diff --git a/db/post_migrate/20240613154931_add_vulnerability_flags_project_id_fk.rb b/db/post_migrate/20240613154931_add_vulnerability_flags_project_id_fk.rb new file mode 100644 index 00000000000..ec3189914b3 --- /dev/null +++ b/db/post_migrate/20240613154931_add_vulnerability_flags_project_id_fk.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddVulnerabilityFlagsProjectIdFk < Gitlab::Database::Migration[2.2] + milestone '17.2' + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :vulnerability_flags, :projects, column: :project_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :vulnerability_flags, column: :project_id + end + end +end diff --git a/db/post_migrate/20240613154932_add_vulnerability_flags_project_id_trigger.rb b/db/post_migrate/20240613154932_add_vulnerability_flags_project_id_trigger.rb new file mode 100644 index 00000000000..8cb03c35153 --- /dev/null +++ b/db/post_migrate/20240613154932_add_vulnerability_flags_project_id_trigger.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class AddVulnerabilityFlagsProjectIdTrigger < Gitlab::Database::Migration[2.2] + milestone '17.2' + + def up + install_sharding_key_assignment_trigger( + table: :vulnerability_flags, + sharding_key: :project_id, + parent_table: :vulnerability_occurrences, + parent_sharding_key: :project_id, + foreign_key: :vulnerability_occurrence_id + ) + end + + def down + remove_sharding_key_assignment_trigger( + table: :vulnerability_flags, + sharding_key: :project_id, + parent_table: :vulnerability_occurrences, + parent_sharding_key: :project_id, + foreign_key: :vulnerability_occurrence_id + ) + end +end diff --git a/db/post_migrate/20240613154933_queue_backfill_vulnerability_flags_project_id.rb b/db/post_migrate/20240613154933_queue_backfill_vulnerability_flags_project_id.rb new file mode 100644 index 00000000000..ee6ae872b81 --- /dev/null +++ b/db/post_migrate/20240613154933_queue_backfill_vulnerability_flags_project_id.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +class QueueBackfillVulnerabilityFlagsProjectId < Gitlab::Database::Migration[2.2] + milestone '17.2' + restrict_gitlab_migration gitlab_schema: :gitlab_main_cell + + MIGRATION = "BackfillVulnerabilityFlagsProjectId" + DELAY_INTERVAL = 2.minutes + BATCH_SIZE = 1000 + SUB_BATCH_SIZE = 100 + + def up + queue_batched_background_migration( + MIGRATION, + :vulnerability_flags, + :id, + :project_id, + :vulnerability_occurrences, + :project_id, + :vulnerability_occurrence_id, + job_interval: DELAY_INTERVAL, + batch_size: BATCH_SIZE, + sub_batch_size: SUB_BATCH_SIZE + ) + end + + def down + delete_batched_background_migration( + MIGRATION, + :vulnerability_flags, + :id, + [ + :project_id, + :vulnerability_occurrences, + :project_id, + :vulnerability_occurrence_id + ] + ) + end +end diff --git a/db/schema_migrations/20240613152414 b/db/schema_migrations/20240613152414 new file mode 100644 index 00000000000..9ce71076584 --- /dev/null +++ b/db/schema_migrations/20240613152414 @@ -0,0 +1 @@ +361cff5b8f21843d56d59de4af3f3854fd75fb2c43bcea4f0b9cb62cac3e80ac \ No newline at end of file diff --git a/db/schema_migrations/20240613154929 b/db/schema_migrations/20240613154929 new file mode 100644 index 00000000000..5d8b5a3c343 --- /dev/null +++ b/db/schema_migrations/20240613154929 @@ -0,0 +1 @@ +a35fea9269a434e53ecd96f88e55532297e26dde432a48bbbfa7e47c166cd635 \ No newline at end of file diff --git a/db/schema_migrations/20240613154930 b/db/schema_migrations/20240613154930 new file mode 100644 index 00000000000..4f6f952c1ca --- /dev/null +++ b/db/schema_migrations/20240613154930 @@ -0,0 +1 @@ +0325637342ca20cfadcf95be421b38672a5ba42c765d3abea8409bb299d83f38 \ No newline at end of file diff --git a/db/schema_migrations/20240613154931 b/db/schema_migrations/20240613154931 new file mode 100644 index 00000000000..ca9739b0bf8 --- /dev/null +++ b/db/schema_migrations/20240613154931 @@ -0,0 +1 @@ +aeb4a949fd02dae9965ee17fbd86c363ac8cf2de6aba1ef120e60340ee0fd2eb \ No newline at end of file diff --git a/db/schema_migrations/20240613154932 b/db/schema_migrations/20240613154932 new file mode 100644 index 00000000000..fd2bc710d87 --- /dev/null +++ b/db/schema_migrations/20240613154932 @@ -0,0 +1 @@ +f214e74808ffd5ae207dc91bc75574796043b6d365fb6b0c6c893316ae0454c9 \ No newline at end of file diff --git a/db/schema_migrations/20240613154933 b/db/schema_migrations/20240613154933 new file mode 100644 index 00000000000..8b9e5cdad86 --- /dev/null +++ b/db/schema_migrations/20240613154933 @@ -0,0 +1 @@ +ee02fe65bcde3710f317b9921fe0371591d1278144aafa47350c720edc31364e \ No newline at end of file diff --git a/db/schema_migrations/20240617100206 b/db/schema_migrations/20240617100206 new file mode 100644 index 00000000000..a04020854de --- /dev/null +++ b/db/schema_migrations/20240617100206 @@ -0,0 +1 @@ +e156b2eab4ba787def1619ed81263615b11353544980cb6d05850f1773279041 \ No newline at end of file diff --git a/db/schema_migrations/20240617101253 b/db/schema_migrations/20240617101253 new file mode 100644 index 00000000000..c50f7e36523 --- /dev/null +++ b/db/schema_migrations/20240617101253 @@ -0,0 +1 @@ +4b9ccafeaf4e81b843b357581f466f15a268bc520a013ff06778ef1dc5f6faf6 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 647d253f3ff..2078fdfaeee 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1505,6 +1505,22 @@ RETURN NEW; END $$; +CREATE FUNCTION trigger_dc13168b8025() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN +IF NEW."project_id" IS NULL THEN + SELECT "project_id" + INTO NEW."project_id" + FROM "vulnerability_occurrences" + WHERE "vulnerability_occurrences"."id" = NEW."vulnerability_occurrence_id"; +END IF; + +RETURN NEW; + +END +$$; + CREATE FUNCTION trigger_ebab34f83f1d() RETURNS trigger LANGUAGE plpgsql AS $$ @@ -7855,7 +7871,8 @@ CREATE TABLE ci_runner_projects ( runner_id integer NOT NULL, created_at timestamp without time zone, updated_at timestamp without time zone, - project_id integer + project_id integer, + CONSTRAINT check_db297016c6 CHECK ((project_id IS NOT NULL)) ); CREATE SEQUENCE ci_runner_projects_id_seq @@ -18024,6 +18041,12 @@ CREATE TABLE user_credit_card_validations ( expiration_date_hash text, network_hash text, zuora_payment_method_xid text, + stripe_setup_intent_xid text, + stripe_payment_method_xid text, + stripe_card_fingerprint text, + CONSTRAINT check_126615a57d CHECK ((char_length(stripe_payment_method_xid) <= 255)), + CONSTRAINT check_209503e313 CHECK ((char_length(stripe_card_fingerprint) <= 255)), + CONSTRAINT check_5d9e69ede5 CHECK ((char_length(stripe_setup_intent_xid) <= 255)), CONSTRAINT check_7721e1961a CHECK ((char_length(network_hash) <= 44)), CONSTRAINT check_83f1e2ace3 CHECK ((char_length(expiration_date_hash) <= 44)), CONSTRAINT check_9a15d14e37 CHECK ((char_length(zuora_payment_method_xid) <= 50)), @@ -18629,6 +18652,7 @@ CREATE TABLE vulnerability_flags ( flag_type smallint DEFAULT 0 NOT NULL, origin text NOT NULL, description text NOT NULL, + project_id bigint, CONSTRAINT check_45e743349f CHECK ((char_length(description) <= 1024)), CONSTRAINT check_49c1d00032 CHECK ((char_length(origin) <= 255)) ); @@ -28772,6 +28796,8 @@ CREATE UNIQUE INDEX index_user_canonical_emails_on_user_id ON user_canonical_ema CREATE UNIQUE INDEX index_user_canonical_emails_on_user_id_and_canonical_email ON user_canonical_emails USING btree (user_id, canonical_email); +CREATE INDEX index_user_credit_card_validations_on_stripe_card_fingerprint ON user_credit_card_validations USING btree (stripe_card_fingerprint); + CREATE INDEX index_user_custom_attributes_on_key_and_value ON user_custom_attributes USING btree (key, value); CREATE UNIQUE INDEX index_user_custom_attributes_on_user_id_and_key ON user_custom_attributes USING btree (user_id, key); @@ -28962,6 +28988,8 @@ CREATE INDEX index_vulnerability_findings_remediations_on_remediation_id ON vuln CREATE UNIQUE INDEX index_vulnerability_findings_remediations_on_unique_keys ON vulnerability_findings_remediations USING btree (vulnerability_occurrence_id, vulnerability_remediation_id); +CREATE INDEX index_vulnerability_flags_on_project_id ON vulnerability_flags USING btree (project_id); + CREATE UNIQUE INDEX index_vulnerability_flags_on_unique_columns ON vulnerability_flags USING btree (vulnerability_occurrence_id, flag_type, origin); CREATE INDEX index_vulnerability_flags_on_vulnerability_occurrence_id ON vulnerability_flags USING btree (vulnerability_occurrence_id); @@ -31108,6 +31136,8 @@ CREATE TRIGGER trigger_dadd660afe2c BEFORE INSERT OR UPDATE ON packages_debian_g CREATE TRIGGER trigger_dbdd61a66a91 BEFORE INSERT OR UPDATE ON agent_activity_events FOR EACH ROW EXECUTE FUNCTION trigger_dbdd61a66a91(); +CREATE TRIGGER trigger_dc13168b8025 BEFORE INSERT OR UPDATE ON vulnerability_flags FOR EACH ROW EXECUTE FUNCTION trigger_dc13168b8025(); + CREATE TRIGGER trigger_delete_project_namespace_on_project_delete AFTER DELETE ON projects FOR EACH ROW WHEN ((old.project_namespace_id IS NOT NULL)) EXECUTE FUNCTION delete_associated_project_namespace(); CREATE TRIGGER trigger_ebab34f83f1d BEFORE INSERT OR UPDATE ON packages_debian_publications FOR EACH ROW EXECUTE FUNCTION trigger_ebab34f83f1d(); @@ -32221,6 +32251,9 @@ ALTER TABLE ONLY external_status_checks_protected_branches ALTER TABLE ONLY dast_profiles_pipelines ADD CONSTRAINT fk_cc206a8c13 FOREIGN KEY (dast_profile_id) REFERENCES dast_profiles(id) ON DELETE CASCADE; +ALTER TABLE ONLY vulnerability_flags + ADD CONSTRAINT fk_cc3b7b4548 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; + ALTER TABLE ONLY todos ADD CONSTRAINT fk_ccf0373936 FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE; diff --git a/doc/administration/geo/index.md b/doc/administration/geo/index.md index 05b73918c8b..41b0d16aa2d 100644 --- a/doc/administration/geo/index.md +++ b/doc/administration/geo/index.md @@ -206,7 +206,7 @@ This list of limitations only reflects the latest version of GitLab. If you are GitLab instances based on our [Reference Architectures](../reference_architectures/index.md), including automation of common daily tasks. [Epic 1465](https://gitlab.com/groups/gitlab-org/-/epics/1465) proposes to improve Geo installation even more. - Real-time updates of issues/merge requests (for example, via long polling) doesn't work on the **secondary** site. -- [Selective synchronization](replication/configuration.md#selective-synchronization) only limits what repositories and files are replicated. The entire PostgreSQL data is still replicated. Selective synchronization is not built to accommodate compliance / export control use cases. +- [Selective synchronization](replication/selective_synchronization.md) only limits what repositories and files are replicated. The entire PostgreSQL data is still replicated. Selective synchronization is not built to accommodate compliance / export control use cases. - [Pages access control](../../user/project/pages/pages_access_control.md) doesn't work on secondaries. See [GitLab issue #9336](https://gitlab.com/gitlab-org/gitlab/-/issues/9336) for details. - [Disaster recovery](disaster_recovery/index.md) for deployments that have multiple secondary sites causes downtime due to the need to perform complete re-synchronization and re-configuration of all non-promoted secondaries to follow the new primary site. - For Git over SSH, to make the project clone URL display correctly regardless of which site you are browsing, secondary sites must use the same port as the primary. [GitLab issue #339262](https://gitlab.com/gitlab-org/gitlab/-/issues/339262) proposes to remove this limitation. diff --git a/doc/administration/geo/replication/configuration.md b/doc/administration/geo/replication/configuration.md index fc077663795..cb2003442ee 100644 --- a/doc/administration/geo/replication/configuration.md +++ b/doc/administration/geo/replication/configuration.md @@ -4,36 +4,38 @@ group: Geo info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Geo configuration +# Configure a new **secondary** site DETAILS: **Tier:** Premium, Ultimate **Offering:** Self-managed -## Configuring a new **secondary** site - NOTE: This is the final step in setting up a **secondary** Geo site. Stages of the setup process must be completed in the documented order. If not, [complete all prior stages](../setup/index.md#using-linux-package-installations) before proceeding. -Make sure you [set up the database replication](../setup/database.md), and [configured fast lookup of authorized SSH keys](../../operations/fast_ssh_key_lookup.md) in **both primary and secondary sites**. - The basic steps of configuring a **secondary** site are to: -- Replicate required configurations between the **primary** site and the **secondary** sites. -- Configure a tracking database on each **secondary** site. -- Start GitLab on each **secondary** site. +1. Replicate required configurations between the **primary** and the **secondary** site. +1. Configure a tracking database on each **secondary** site. +1. Start GitLab on each **secondary** site. -You are encouraged to first read through all the steps before executing them -in your testing/production environment. +This document focuses on the first item. You are encouraged to first read +through all the steps before executing them in your testing/production +environment. + +Prerequisites for **both primary and secondary sites**: + +- [Set up the database replication](../setup/database.md) +- [Configure fast lookup of authorized SSH keys](../../operations/fast_ssh_key_lookup.md) NOTE: -**Do not** set up any custom authentication for the **secondary** sites. This is handled by the **primary** site. +**Do not** set up any custom authentication for the **secondary** site. This is handled by the **primary** site. Any change that requires access to the **Admin Area** needs to be done in the **primary** site because the **secondary** site is a read-only replica. -### Step 1. Manually replicate secret GitLab values +## Step 1. Manually replicate secret GitLab values GitLab stores a number of secret values in the `/etc/gitlab/gitlab-secrets.json` file which *must* be the same on all of a site's nodes. Until there is @@ -84,7 +86,7 @@ they must be manually replicated to **all nodes of the secondary site**. gitlab-ctl restart ``` -### Step 2. Manually replicate the **primary** site's SSH host keys +## Step 2. Manually replicate the **primary** site's SSH host keys GitLab integrates with the system-installed SSH daemon, designating a user (typically named `git`) through which all access requests are handled. @@ -195,7 +197,7 @@ In the following steps, replace `` with the one you're using: SSH into your GitLab **secondary** server in a new terminal. If you are unable to connect, verify the permissions are correct according to the previous steps. -### Step 3. Add the **secondary** site +## Step 3. Add the **secondary** site 1. SSH into **each Rails and Sidekiq node on your secondary** site and login as root: @@ -233,7 +235,7 @@ In the following steps, replace `` with the one you're using: 1. Optional. In **Internal URL (optional)**, enter an internal URL for the secondary site. 1. Optional. Select which groups or storage shards should be replicated by the **secondary** site. Leave blank to replicate all. For more information, see - [selective synchronization](#selective-synchronization). + [selective synchronization](selective_synchronization.md). 1. Select **Save changes** to add the **secondary** site. 1. SSH into **each Rails, and Sidekiq node on your secondary** site and restart the services: @@ -267,14 +269,14 @@ that the **secondary** site can act on those notifications immediately. Be sure the _secondary_ site is running and accessible. You can sign in to the _secondary_ site with the same credentials as were used with the _primary_ site. -### Step 4. (Optional) Using custom certificates +## Step 4. (Optional) Using custom certificates You can safely skip this step if: - Your **primary** site uses a public CA-issued HTTPS certificate. - Your **primary** site only connects to external services with CA-issued (not self-signed) HTTPS certificates. -#### Custom or self-signed certificate for inbound connections +### Custom or self-signed certificate for inbound connections If your GitLab Geo **primary** site uses a custom or [self-signed certificate to secure inbound HTTPS connections](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates), this can be either a single-domain or multi-domain certificate. @@ -283,7 +285,7 @@ Install the correct certificate based on your certificate type: - **Multi-domain certificate** that includes both primary and secondary site domains: Install the certificate at `/etc/gitlab/ssl` on all **Rails, Sidekiq, and Gitaly** nodes in the **secondary** site. - **Single-domain certificate** where the certificates are specific to each Geo site domain: Generate a valid certificate for your **secondary** site's domain and install it at `/etc/gitlab/ssl` following [these instructions](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates) on all **Rails, Sidekiq, and Gitaly** nodes in the **secondary** site. -#### Connecting to external services that use custom certificates +### Connecting to external services that use custom certificates A copy of the self-signed certificate for the external service needs to be added to the trust store on all the **primary** site's nodes that require access to the service. @@ -322,7 +324,7 @@ If your **primary** site is using a [custom or self-signed certificate for inbou sudo gitlab-ctl reconfigure ``` -### Step 5. Enable Git access over HTTP/HTTPS and SSH +## Step 5. Enable Git access over HTTP/HTTPS and SSH Geo synchronizes repositories over HTTP/HTTPS, and therefore requires this clone method to be enabled. This is enabled by default, but if converting an existing site to Geo it should be checked: @@ -337,7 +339,7 @@ On the **primary** site: 1. Follow [Fast lookup of authorized SSH keys in the database](../../operations/fast_ssh_key_lookup.md) on **all primary and secondary** sites. 1. If not using Git over SSH, then set "Enabled Git access protocols" to "Only HTTP(S)". -### Step 6. Verify proper functioning of the **secondary** site +## Step 6. Verify proper functioning of the **secondary** site You can sign in to the **secondary** site with the same credentials you used with the **primary** site. After you sign in: @@ -379,43 +381,6 @@ Currently, this is what is synced: - Issues, merge requests, snippets, and comment attachments. - Users, groups, and project avatars. -## Selective synchronization - -Geo supports selective synchronization, which allows administrators to choose -which projects should be synchronized by **secondary** sites. -A subset of projects can be chosen, either by group or by storage shard. The -former is ideal for replicating data belonging to a subset of users, while the -latter is more suited to progressively rolling out Geo to a large GitLab -instance. - -NOTE: -Geo's synchronization logic is outlined in the [documentation](../index.md). Both the solution and the documentation is subject to change from time to time. You must independently determine your legal obligations in regard to privacy and cybersecurity laws, and applicable trade control law on an ongoing basis. - -Selective synchronization: - -1. Does not restrict permissions from **secondary** sites. -1. Does not hide project metadata from **secondary** sites. - - Since Geo currently relies on PostgreSQL replication, all project metadata - gets replicated to **secondary** sites, but repositories that have not been - selected are empty. -1. Does not reduce the number of events generated for the Geo event log. - - The **primary** site generates events as long as any **secondary** sites are present. - Selective synchronization restrictions are implemented on the **secondary** sites, - not the **primary** site. - -### Git operations on unreplicated repositories - -Git clone, pull, and push operations over HTTP(S) and SSH are supported for repositories that -exist on the **primary** site but not on **secondary** sites. This situation can occur -when: - -- Selective synchronization does not include the project attached to the repository. -- The repository is actively being replicated but has not completed yet. - -## Upgrading Geo - -See the [upgrading the Geo sites document](upgrading_the_geo_sites.md). - ## Troubleshooting See the [troubleshooting document](troubleshooting.md). diff --git a/doc/administration/geo/replication/faq.md b/doc/administration/geo/replication/faq.md index 0c5d78d6f98..45b58f42042 100644 --- a/doc/administration/geo/replication/faq.md +++ b/doc/administration/geo/replication/faq.md @@ -83,11 +83,11 @@ No, Geo sites can be based on different reference architectures. For example, yo ## Does Geo replicate archived projects? -Yes, provided they are not excluded through [selective sync](../replication/configuration.md#selective-synchronization). +Yes, provided they are not excluded through [selective sync](../replication/selective_synchronization.md). ## Does Geo replicate personal projects? -Yes, provided they are not excluded through [selective sync](../replication/configuration.md#selective-synchronization). +Yes, provided they are not excluded through [selective sync](../replication/selective_synchronization.md). ## Are delayed deletion projects replicated to secondary sites? diff --git a/doc/administration/geo/replication/selective_synchronization.md b/doc/administration/geo/replication/selective_synchronization.md new file mode 100644 index 00000000000..3dadfc60665 --- /dev/null +++ b/doc/administration/geo/replication/selective_synchronization.md @@ -0,0 +1,42 @@ +--- +stage: Systems +group: Geo +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Selective synchronization + +DETAILS: +**Tier:** Premium, Ultimate +**Offering:** Self-managed + +Geo supports selective synchronization, which allows administrators to choose +which projects should be synchronized by **secondary** sites. +A subset of projects can be chosen, either by group or by storage shard. The +former is ideal for replicating data belonging to a subset of users, while the +latter is more suited to progressively rolling out Geo to a large GitLab +instance. + +NOTE: +Geo's synchronization logic is outlined in the [documentation](../index.md). Both the solution and the documentation is subject to change from time to time. You must independently determine your legal obligations in regard to privacy and cybersecurity laws, and applicable trade control law on an ongoing basis. + +Selective synchronization: + +1. Does not restrict permissions from **secondary** sites. +1. Does not hide project metadata from **secondary** sites. + - Since Geo relies on PostgreSQL replication, all project metadata + gets replicated to **secondary** sites, but repositories that have not been + selected are empty. +1. Does not reduce the number of events generated for the Geo event log. + - The **primary** site generates events as long as any **secondary** sites are present. + Selective synchronization restrictions are implemented on the **secondary** sites, + not the **primary** site. + +## Git operations on unreplicated repositories + +Git clone, pull, and push operations over HTTP(S) and SSH are supported for repositories that +exist on the **primary** site but not on **secondary** sites. This situation can occur +when: + +- Selective synchronization does not include the project attached to the repository. +- The repository is actively being replicated but has not completed yet. diff --git a/doc/administration/geo/setup/two_single_node_external_services.md b/doc/administration/geo/setup/two_single_node_external_services.md index 577aaf76360..5c6f8bc4971 100644 --- a/doc/administration/geo/setup/two_single_node_external_services.md +++ b/doc/administration/geo/setup/two_single_node_external_services.md @@ -323,7 +323,7 @@ secondary site is a read-only copy. match exactly. 1. Optional. In **Internal URL (optional)**, enter an internal URL for the primary site. 1. Optional. Select which groups or storage shards should be replicated by the - secondary site. To replicate all, leave the field blank. See [selective synchronization](../replication/configuration.md#selective-synchronization). + secondary site. To replicate all, leave the field blank. See [selective synchronization](../replication/selective_synchronization.md). 1. Select **Save changes**. 1. SSH into each Rails and Sidekiq node on your secondary site and restart the services: diff --git a/doc/administration/geo/setup/two_single_node_sites.md b/doc/administration/geo/setup/two_single_node_sites.md index f6c6a89377b..327ffe258fd 100644 --- a/doc/administration/geo/setup/two_single_node_sites.md +++ b/doc/administration/geo/setup/two_single_node_sites.md @@ -574,7 +574,7 @@ You must manually replicate the secret file across all of your secondary sites, match exactly. 1. Optional. In **Internal URL (optional)**, enter an internal URL for the primary site. 1. Optional. Select which groups or storage shards should be replicated by the - secondary site. To replicate all, leave the field blank. See [selective synchronization](../replication/configuration.md#selective-synchronization). + secondary site. To replicate all, leave the field blank. See [selective synchronization](../replication/selective_synchronization.md). 1. Select **Save changes**. 1. SSH into each Rails and Sidekiq node on your secondary site and restart the services: diff --git a/doc/administration/geo_sites.md b/doc/administration/geo_sites.md index e9f0a81557d..1866663f4ec 100644 --- a/doc/administration/geo_sites.md +++ b/doc/administration/geo_sites.md @@ -37,7 +37,7 @@ the **primary** node is listed first as `Primary site`. | Setting | Description | |---------------------------|-------------| -| Selective synchronization | Enable Geo [selective sync](../administration/geo/replication/configuration.md#selective-synchronization) for this **secondary** site. | +| Selective synchronization | Enable Geo [selective sync](../administration/geo/replication/selective_synchronization.md) for this **secondary** site. | | Repository sync capacity | Number of concurrent requests this **secondary** site makes to the **primary** site when backfilling repositories. | | File sync capacity | Number of concurrent requests this **secondary** site makes to the **primary** site when backfilling files. | diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 5b7e8ec066f..4d797154fcb 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -16035,7 +16035,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -16079,7 +16080,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -16177,7 +16179,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -16845,7 +16848,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -16889,7 +16893,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -16999,7 +17004,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -19043,7 +19049,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -19087,7 +19094,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -19185,7 +19193,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -22392,7 +22401,8 @@ four standard [pagination arguments](#pagination-arguments): | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | | `includeArchived` | [`Boolean`](#boolean) | Return merge requests from archived projects. | | `includeSubgroups` | [`Boolean`](#boolean) | Include merge requests belonging to subgroups. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -24360,7 +24370,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -24404,7 +24415,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -24502,7 +24514,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -24702,7 +24715,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -24746,7 +24760,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -24844,7 +24859,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -25091,7 +25107,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -25135,7 +25152,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -25233,7 +25251,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -25469,7 +25488,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -25513,7 +25533,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -25611,7 +25632,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -28539,7 +28561,8 @@ four standard [pagination arguments](#pagination-arguments): | `deploymentId` | [`String`](#string) | ID of the deployment. | | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -31324,7 +31347,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -31368,7 +31392,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -31466,7 +31491,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -37759,7 +37785,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -37803,7 +37830,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | @@ -37901,7 +37929,8 @@ four standard [pagination arguments](#pagination-arguments): | `draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | `groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | | `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `labelName` | [`[String]`](#string) | Labels applied to the merge request. | +| `labels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.1. Use `labelName`. | | `mergedAfter` | [`Time`](#time) | Merge requests merged after the date. | | `mergedBefore` | [`Time`](#time) | Merge requests merged before the date. | | `milestoneTitle` | [`String`](#string) | Title of the milestone. Incompatible with milestoneWildcardId. | diff --git a/doc/api/project_packages_protection_rules.md b/doc/api/project_packages_protection_rules.md index 11997b1f295..77d6811a55b 100644 --- a/doc/api/project_packages_protection_rules.md +++ b/doc/api/project_packages_protection_rules.md @@ -9,7 +9,7 @@ description: "Documentation for the REST API for Package Protection Rules in Git DETAILS: **Tier:** Free, Premium, Ultimate -**Offering:** GitLab.com, Self-managed +**Offering:** Self-managed **Status:** Experiment > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151741) in GitLab 17.1 [with a flag](../administration/feature_flags.md) named `packages_protected_packages`. Disabled by default. @@ -72,6 +72,48 @@ Example response: ] ``` +## Create a package protection rule + +Create a package protection rule for a project. + +```plaintext +POST /api/v4/projects/:id/packages/protection/rules +``` + +Supported attributes: + +| Attribute | Type | Required | Description | +|---------------------------------------|-----------------|----------|--------------------------------| +| `id` | integer/string | Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. | +| `package_name_pattern` | string | Yes | Package name protected by the protection rule. For example `@my-scope/my-package-*`. Wildcard character `*` allowed. | +| `package_type` | string | Yes | Package type protected by the protection rule. For example `npm`. | +| `minimum_access_level_for_push` | string | Yes | Minimum GitLab access level able to push a package. For example `developer`, `maintainer`, `owner`. | + +If successful, returns [`201`](rest/index.md#status-codes) and the created package protection rule. + +Can return the following status codes: + +- `201 Created`: The package protection rule was created successfully. +- `400 Bad Request`: The package protection rule is invalid. +- `401 Unauthorized`: The access token is invalid. +- `403 Forbidden`: The user does not have permission to create a package protection rule. +- `404 Not Found`: The project was not found. +- `422 Unprocessable Entity`: The package protection rule could not be created, for example, because the `package_name_pattern` is already taken. + +Example request: + +```shell +curl --request POST \ + --header "PRIVATE-TOKEN: " \ + --header "Content-Type: application/json" \ + --url "https://gitlab.example.com/api/v4/projects/7/packages/protection/rules" \ + --data '{ + "package_name_pattern": "package-name-pattern-*", + "package_type": "npm", + "minimum_access_level_for_push": "maintainer" + }' +``` + ## Delete a package protection rule Deletes a package protection rule from a project. diff --git a/doc/architecture/blueprints/ai_gateway/index.md b/doc/architecture/blueprints/ai_gateway/index.md index 56447555a65..dac5a921c87 100644 --- a/doc/architecture/blueprints/ai_gateway/index.md +++ b/doc/architecture/blueprints/ai_gateway/index.md @@ -582,9 +582,7 @@ In the next iteration, we plan to decompose the Chat primitive into multiple pri The introduction of Unit Primitives will simplify the management of AI features and provide a more granular control over the functionalities exposed through the AI Gateway. This will also pave the way for future work on supporting user-deployed models and locally hosted models. -For more details, refer to the [Initial Set of Unit Primitives](https://gitlab.com/gitlab-org/gitlab/-/issues/444934) issue. - -- [Unit Primitives for Accessing CC Features](https://gitlab.com/groups/gitlab-org/-/epics/12556) +For more details on how to implement and integrate unit primitives, refer to the [Cloud Connector documentation](../../../development/cloud_connector/index.md). ### Self Managed AI Gateway diff --git a/lib/api/project_packages_protection_rules.rb b/lib/api/project_packages_protection_rules.rb index bcabecf47f6..a86ac8fb076 100644 --- a/lib/api/project_packages_protection_rules.rb +++ b/lib/api/project_packages_protection_rules.rb @@ -32,6 +32,35 @@ module API present user_project.package_protection_rules, with: Entities::Projects::Packages::Protection::Rule end + desc 'Create a package protection rule for a project' do + success Entities::Projects::Packages::Protection::Rule + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' }, + { code: 422, message: 'Unprocessable Entity' } + ] + tags %w[projects] + end + params do + requires :package_name_pattern, type: String, + desc: 'Package name protected by the rule. For example @my-scope/my-package-*. Wildcard character * allowed.' + requires :package_type, type: String, values: Packages::Protection::Rule.package_types.keys, + desc: 'Package type protected by the rule. For example npm.' + requires :minimum_access_level_for_push, type: String, + values: Packages::Protection::Rule.minimum_access_level_for_pushes.keys, + desc: 'Minimum GitLab access level able to push a package. For example developer, maintainer, owner.' + end + post ':id/packages/protection/rules' do + response = ::Packages::Protection::CreateRuleService.new(project: user_project, current_user: current_user, + params: declared_params(params)).execute + + render_api_error!({ error: response.message }, :unprocessable_entity) if response.error? + + present response[:package_protection_rule], with: Entities::Projects::Packages::Protection::Rule + end + desc 'Delete package protection rule' do success code: 204, message: '204 No Content' failure [ diff --git a/lib/api/users.rb b/lib/api/users.rb index 447d5aa4ac4..2bc51836ab1 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -1301,6 +1301,9 @@ module API requires :credit_card_type, type: String, desc: 'The credit card network name' optional :zuora_payment_method_xid, type: String, desc: 'The Zuora payment method ID' + optional :stripe_setup_intent_xid, type: String, desc: 'The Stripe setup intent ID' + optional :stripe_payment_method_xid, type: String, desc: 'The Stripe payment method ID' + optional :stripe_card_fingerprint, type: String, desc: 'The Stripe credit card fingerprint' end put ":user_id/credit_card_validation", urgency: :low, feature_category: :subscription_management do authenticated_as_admin! diff --git a/lib/gitlab/background_migration/backfill_vulnerability_flags_project_id.rb b/lib/gitlab/background_migration/backfill_vulnerability_flags_project_id.rb new file mode 100644 index 00000000000..c732b844bc3 --- /dev/null +++ b/lib/gitlab/background_migration/backfill_vulnerability_flags_project_id.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + class BackfillVulnerabilityFlagsProjectId < BackfillDesiredShardingKeyJob + operation_name :backfill_vulnerability_flags_project_id + feature_category :vulnerability_management + end + end +end diff --git a/package.json b/package.json index eb011e61b36..7436ccbf9c8 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@gitlab/favicon-overlay": "2.0.0", "@gitlab/fonts": "^1.3.0", "@gitlab/ui": "81.0.0", - "@gitlab/svgs": "3.102.0", + "@gitlab/svgs": "3.103.0", "@gitlab/web-ide": "^0.0.1-dev-20240531032328", "@mattiasbuelens/web-streams-adapter": "^0.1.0", "@rails/actioncable": "7.0.8-4", diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index c01d0e795f5..a77a68b37d9 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -743,7 +743,7 @@ module QA def enabled?(value, default: true) return default if value.nil? - (value.to_s =~ /^(false|no|0)$/i) != 0 + value.to_s.match?(/^(true|yes|1)$/i) end end end diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index afd7146a81a..e374ea3d7ed 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -9,26 +9,29 @@ RSpec.describe QA::Runtime::Env do shared_examples 'boolean method with parameter' do |method:, env_key:, default:, param: nil| context 'when there is an env variable set' do - it 'returns false when falsey values specified' do + it 'returns false when variable is falsey or unsupported' do stub_env(env_key, 'false') - expect(described_class.public_send(method, *param)).to be_falsey + expect(described_class.public_send(method, *param)).to eq(false) stub_env(env_key, 'no') - expect(described_class.public_send(method, *param)).to be_falsey + expect(described_class.public_send(method, *param)).to eq(false) stub_env(env_key, '0') - expect(described_class.public_send(method, *param)).to be_falsey - end - - it 'returns true when anything else specified' do - stub_env(env_key, 'true') - expect(described_class.public_send(method, *param)).to be_truthy - - stub_env(env_key, '1') - expect(described_class.public_send(method, *param)).to be_truthy + expect(described_class.public_send(method, *param)).to eq(false) stub_env(env_key, 'anything') - expect(described_class.public_send(method, *param)).to be_truthy + expect(described_class.public_send(method, *param)).to eq(false) + end + + it 'returns true when variable set to truthy' do + stub_env(env_key, 'true') + expect(described_class.public_send(method, *param)).to eq(true) + + stub_env(env_key, '1') + expect(described_class.public_send(method, *param)).to eq(true) + + stub_env(env_key, 'yes') + expect(described_class.public_send(method, *param)).to eq(true) end end diff --git a/scripts/generate-e2e-pipeline b/scripts/generate-e2e-pipeline index d6e7107ac66..d4a62ca44ec 100755 --- a/scripts/generate-e2e-pipeline +++ b/scripts/generate-e2e-pipeline @@ -49,7 +49,7 @@ variables: QA_SUITES: "$QA_SUITES" QA_TESTS: "$QA_TESTS" KNAPSACK_TEST_FILE_PATTERN: "$KNAPSACK_TEST_FILE_PATTERN" - COVERBAND_ENABLED: "$COVERBAND_ENABLED" + COVERBAND_ENABLED: "${COVERBAND_ENABLED:-false}" YML ) diff --git a/spec/features/ics/dashboard_issues_spec.rb b/spec/features/ics/dashboard_issues_spec.rb index 00318f83105..adfc3717a97 100644 --- a/spec/features/ics/dashboard_issues_spec.rb +++ b/spec/features/ics/dashboard_issues_spec.rb @@ -127,7 +127,7 @@ RSpec.describe 'Dashboard Issues Calendar Feed', feature_category: :team_plannin expected_description = (+"DESCRIPTION:Find out more at #{issue_url(issue)}").insert(75, ' ') expect(body).to have_text(expected_description) expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}") - expect(body).to have_text("URL:#{issue_url(issue)}") + expect(body).to have_text("URI:#{issue_url(issue)}") expect(body).to have_text('TRANSP:TRANSPARENT') end end diff --git a/spec/features/ics/group_issues_spec.rb b/spec/features/ics/group_issues_spec.rb index ce9f5638a00..9f61d94e25f 100644 --- a/spec/features/ics/group_issues_spec.rb +++ b/spec/features/ics/group_issues_spec.rb @@ -90,7 +90,7 @@ RSpec.describe 'Group Issues Calendar Feed', feature_category: :groups_and_proje expected_description = (+"DESCRIPTION:Find out more at #{issue_url(issue)}").insert(75, ' ') expect(body).to have_text(expected_description) expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}") - expect(body).to have_text("URL:#{issue_url(issue)}") + expect(body).to have_text("URI:#{issue_url(issue)}") expect(body).to have_text('TRANSP:TRANSPARENT') end end diff --git a/spec/features/ics/project_issues_spec.rb b/spec/features/ics/project_issues_spec.rb index c26147e0310..f3ba2cd4a28 100644 --- a/spec/features/ics/project_issues_spec.rb +++ b/spec/features/ics/project_issues_spec.rb @@ -89,7 +89,7 @@ RSpec.describe 'Project Issues Calendar Feed', feature_category: :groups_and_pro expected_description = (+"DESCRIPTION:Find out more at #{issue_url(issue)}").insert(75, ' ') expect(body).to have_text(expected_description) expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}") - expect(body).to have_text("URL:#{issue_url(issue)}") + expect(body).to have_text("URI:#{issue_url(issue)}") expect(body).to have_text('TRANSP:TRANSPARENT') end end diff --git a/spec/graphql/resolvers/project_merge_requests_resolver_spec.rb b/spec/graphql/resolvers/project_merge_requests_resolver_spec.rb index 207b6c157f2..d59478a5c64 100644 --- a/spec/graphql/resolvers/project_merge_requests_resolver_spec.rb +++ b/spec/graphql/resolvers/project_merge_requests_resolver_spec.rb @@ -9,6 +9,7 @@ RSpec.describe Resolvers::ProjectMergeRequestsResolver do let_it_be(:current_user) { create(:user, developer_of: project) } let_it_be(:other_user) { create(:user) } let_it_be(:reviewer) { create(:user) } + let_it_be(:label) { create(:label, project: project) } let_it_be(:merge_request) do create( @@ -19,7 +20,8 @@ RSpec.describe Resolvers::ProjectMergeRequestsResolver do author: other_user, assignee: other_user, milestone: create(:milestone, project: project), - reviewers: [reviewer] + reviewers: [reviewer], + labels: [label] ) end @@ -138,6 +140,14 @@ RSpec.describe Resolvers::ProjectMergeRequestsResolver do end end + context 'with label name param' do + it 'filters merge requests by label name' do + result = resolve_mr(project, label_name: [label.name]) + + expect(result).to contain_exactly(merge_request) + end + end + def resolve_mr(project, resolver: described_class, user: current_user, **args) resolve(resolver, obj: project, args: args, ctx: { current_user: user }) end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index a216f0a1b80..2f83bfaf4fe 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -341,6 +341,7 @@ RSpec.describe GitlabSchema.types['Project'], feature_category: :groups_and_proj :draft, :approved, :labels, + :label_name, :before, :after, :first, diff --git a/spec/lib/gitlab/background_migration/backfill_vulnerability_flags_project_id_spec.rb b/spec/lib/gitlab/background_migration/backfill_vulnerability_flags_project_id_spec.rb new file mode 100644 index 00000000000..c2629d54df7 --- /dev/null +++ b/spec/lib/gitlab/background_migration/backfill_vulnerability_flags_project_id_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::BackfillVulnerabilityFlagsProjectId, + feature_category: :vulnerability_management, + schema: 20240613154929 do + include_examples 'desired sharding key backfill job' do + let(:batch_table) { :vulnerability_flags } + let(:backfill_column) { :project_id } + let(:backfill_via_table) { :vulnerability_occurrences } + let(:backfill_via_column) { :project_id } + let(:backfill_via_foreign_key) { :vulnerability_occurrence_id } + end +end diff --git a/spec/migrations/20240613154933_queue_backfill_vulnerability_flags_project_id_spec.rb b/spec/migrations/20240613154933_queue_backfill_vulnerability_flags_project_id_spec.rb new file mode 100644 index 00000000000..2600dabf78b --- /dev/null +++ b/spec/migrations/20240613154933_queue_backfill_vulnerability_flags_project_id_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe QueueBackfillVulnerabilityFlagsProjectId, feature_category: :vulnerability_management do + let!(:batched_migration) { described_class::MIGRATION } + + it 'schedules a new batched migration' do + reversible_migration do |migration| + migration.before -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + + migration.after -> { + expect(batched_migration).to have_scheduled_batched_migration( + table_name: :vulnerability_flags, + column_name: :id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE, + gitlab_schema: :gitlab_main_cell, + job_arguments: [ + :project_id, + :vulnerability_occurrences, + :project_id, + :vulnerability_occurrence_id + ] + ) + } + end + end +end diff --git a/spec/requests/api/project_packages_protection_rules_spec.rb b/spec/requests/api/project_packages_protection_rules_spec.rb index 20079cfb867..95e4216f86d 100644 --- a/spec/requests/api/project_packages_protection_rules_spec.rb +++ b/spec/requests/api/project_packages_protection_rules_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProjectPackagesProtectionRules, feature_category: :package_registry do +RSpec.describe API::ProjectPackagesProtectionRules, :aggregate_failures, feature_category: :package_registry do include ExclusiveLeaseHelpers let_it_be(:project) { create(:project, :private) } @@ -15,13 +15,22 @@ RSpec.describe API::ProjectPackagesProtectionRules, feature_category: :package_r let_it_be(:invalid_token) { 'invalid-token123' } let_it_be(:headers_with_invalid_token) { { Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER => invalid_token } } - shared_examples 'rejecting project packages protection rules request' do |user_role, status| - context "for #{user_role}" do + shared_examples 'rejecting project packages protection rules request when not enough permissions' do + using RSpec::Parameterized::TableSyntax + + where(:user_role, :status) do + :reporter | :forbidden + :developer | :forbidden + :guest | :forbidden + nil | :not_found + end + + with_them do before do project.send(:"add_#{user_role}", api_user) if user_role end - it_behaves_like 'returning response status', status + it_behaves_like 'returning response status', params[:status] end end @@ -30,10 +39,7 @@ RSpec.describe API::ProjectPackagesProtectionRules, feature_category: :package_r subject(:get_package_rules) { get(api(url, api_user)) } - it_behaves_like 'rejecting project packages protection rules request', :reporter, :forbidden - it_behaves_like 'rejecting project packages protection rules request', :developer, :forbidden - it_behaves_like 'rejecting project packages protection rules request', :guest, :forbidden - it_behaves_like 'rejecting project packages protection rules request', nil, :not_found + it_behaves_like 'rejecting project packages protection rules request when not enough permissions' context 'for maintainer' do let(:api_user) { maintainer } @@ -52,13 +58,13 @@ RSpec.describe API::ProjectPackagesProtectionRules, feature_category: :package_r context 'when the project id is invalid' do let(:url) { "/projects/invalid/packages/protection/rules" } - it_behaves_like 'rejecting project packages protection rules request', :maintainer, :not_found + it_behaves_like 'returning response status', :not_found end context 'when the project id does not exist' do let(:url) { "/projects/#{non_existing_record_id}/packages/protection/rules" } - it_behaves_like 'rejecting project packages protection rules request', :maintainer, :not_found + it_behaves_like 'returning response status', :not_found end context 'when packages_protected_packages is disabled' do @@ -66,14 +72,89 @@ RSpec.describe API::ProjectPackagesProtectionRules, feature_category: :package_r stub_feature_flags(packages_protected_packages: false) end - it_behaves_like 'rejecting project packages protection rules request', :maintainer, :not_found + it_behaves_like 'returning response status', :not_found end end context 'with invalid token' do subject(:get_package_rules) { get(api(url), headers: headers_with_invalid_token) } - it_behaves_like 'rejecting project packages protection rules request', nil, :unauthorized + it_behaves_like 'returning response status', :unauthorized + end + end + + describe 'POST /projects/:id/packages/protection/rules' do + let(:url) { "/projects/#{project.id}/packages/protection/rules" } + let(:params) do + { package_name_pattern: '@my-new-scope/my-package-*', + package_type: package_protection_rule.package_type, + minimum_access_level_for_push: package_protection_rule.minimum_access_level_for_push } + end + + subject(:post_package_rule) { post(api(url, api_user), params: params) } + + it_behaves_like 'rejecting project packages protection rules request when not enough permissions' + + context 'for maintainer' do + let(:api_user) { maintainer } + + it 'creates a package protection rule' do + expect { post_package_rule }.to change { Packages::Protection::Rule.count }.by(1) + expect(response).to have_gitlab_http_status(:created) + end + + context 'with invalid package_type' do + before do + params[:package_type] = "not in enum" + end + + it 'does not create a package protection rule' do + expect { post_package_rule }.to not_change(Packages::Protection::Rule, :count) + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + context 'with invalid minimum_access_level_for_push' do + before do + params[:minimum_access_level_for_push] = "not in enum" + end + + it 'does not create a package protection rule' do + expect { post_package_rule }.to not_change(Packages::Protection::Rule, :count) + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + context 'with already existing package_name_pattern' do + before do + params[:package_name_pattern] = package_protection_rule.package_name_pattern + end + + it 'does not create a package protection rule' do + expect { post_package_rule }.to not_change(Packages::Protection::Rule, :count) + expect(response).to have_gitlab_http_status(:unprocessable_entity) + end + end + + context 'when the project id is invalid' do + let(:url) { "/projects/invalid/packages/protection/rules" } + + it_behaves_like 'returning response status', :not_found + end + + context 'when the project id does not exist' do + let(:url) { "/projects/#{non_existing_record_id}/packages/protection/rules" } + + it_behaves_like 'returning response status', :not_found + end + + context 'when packages_protected_packages is disabled' do + before do + stub_feature_flags(packages_protected_packages: false) + end + + it_behaves_like 'returning response status', :not_found + end end end @@ -82,10 +163,7 @@ RSpec.describe API::ProjectPackagesProtectionRules, feature_category: :package_r subject(:destroy_package_rule) { delete(api(url, api_user)) } - it_behaves_like 'rejecting project packages protection rules request', :reporter, :forbidden - it_behaves_like 'rejecting project packages protection rules request', :developer, :forbidden - it_behaves_like 'rejecting project packages protection rules request', :guest, :forbidden - it_behaves_like 'rejecting project packages protection rules request', nil, :not_found + it_behaves_like 'rejecting project packages protection rules request when not enough permissions' context 'for maintainer' do let(:api_user) { maintainer } @@ -102,31 +180,31 @@ RSpec.describe API::ProjectPackagesProtectionRules, feature_category: :package_r context 'when the package protection rule does belong to another project' do let(:url) { "/projects/#{other_project.id}/packages/protection/rules/#{package_protection_rule.id}" } - it_behaves_like 'rejecting project packages protection rules request', :maintainer, :not_found + it_behaves_like 'returning response status', :not_found end context 'when the project id is invalid' do let(:url) { "/projects/invalid/packages/protection/rules/#{package_protection_rule.id}" } - it_behaves_like 'rejecting project packages protection rules request', :maintainer, :not_found + it_behaves_like 'returning response status', :not_found end context 'when the project id does not exist' do let(:url) { "/projects/#{non_existing_record_id}/packages/protection/rules/#{package_protection_rule.id}" } - it_behaves_like 'rejecting project packages protection rules request', :maintainer, :not_found + it_behaves_like 'returning response status', :not_found end context 'when the rule id is invalid' do let(:url) { "/projects/#{project.id}/packages/protection/rules/invalid" } - it_behaves_like 'rejecting project packages protection rules request', :maintainer, :bad_request + it_behaves_like 'returning response status', :bad_request end context 'when the rule id does not exist' do let(:url) { "/projects/#{project.id}/packages/protection/rules/#{non_existing_record_id}" } - it_behaves_like 'rejecting project packages protection rules request', :maintainer, :not_found + it_behaves_like 'returning response status', :not_found end context 'when packages_protected_packages is disabled' do @@ -134,13 +212,13 @@ RSpec.describe API::ProjectPackagesProtectionRules, feature_category: :package_r stub_feature_flags(packages_protected_packages: false) end - it_behaves_like 'rejecting project packages protection rules request', :maintainer, :not_found + it_behaves_like 'returning response status', :not_found end context 'with invalid token' do subject(:delete_package_rules) { delete(api(url), headers: headers_with_invalid_token) } - it_behaves_like 'rejecting project packages protection rules request', nil, :unauthorized + it_behaves_like 'returning response status', :unauthorized end end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 72fd992e718..72f62ea51b8 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -2080,6 +2080,9 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_manageme let(:expiration_date) { Date.new(expiration_year, expiration_month, -1) } let(:credit_card_validated_at) { Time.utc(2020, 1, 1) } let(:zuora_payment_method_xid) { 'abc123' } + let(:stripe_setup_intent_xid) { 'seti_abc123' } + let(:stripe_payment_method_xid) { 'pm_abc123' } + let(:stripe_card_fingerprint) { 'card123' } let(:path) { "/user/#{user.id}/credit_card_validation" } let(:params) do @@ -2090,7 +2093,10 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_manageme credit_card_holder_name: holder_name, credit_card_type: network, credit_card_mask_number: last_digits, - zuora_payment_method_xid: zuora_payment_method_xid + zuora_payment_method_xid: zuora_payment_method_xid, + stripe_setup_intent_xid: stripe_setup_intent_xid, + stripe_payment_method_xid: stripe_payment_method_xid, + stripe_card_fingerprint: stripe_card_fingerprint } end @@ -2125,7 +2131,10 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_manageme holder_name_hash: sha256(holder_name.downcase), last_digits_hash: sha256(last_digits), expiration_date_hash: sha256(expiration_date.to_s), - zuora_payment_method_xid: zuora_payment_method_xid + zuora_payment_method_xid: zuora_payment_method_xid, + stripe_setup_intent_xid: stripe_setup_intent_xid, + stripe_payment_method_xid: stripe_payment_method_xid, + stripe_card_fingerprint: stripe_card_fingerprint ) end diff --git a/spec/services/users/upsert_credit_card_validation_service_spec.rb b/spec/services/users/upsert_credit_card_validation_service_spec.rb index d674e1cf818..02e8cad4011 100644 --- a/spec/services/users/upsert_credit_card_validation_service_spec.rb +++ b/spec/services/users/upsert_credit_card_validation_service_spec.rb @@ -17,6 +17,9 @@ RSpec.describe Users::UpsertCreditCardValidationService, feature_category: :user let(:expiration_date) { Date.new(expiration_year, expiration_month, -1) } let(:credit_card_validated_at) { Time.utc(2020, 1, 1) } let(:zuora_payment_method_xid) { 'abc123' } + let(:stripe_setup_intent_xid) { 'seti_abc123' } + let(:stripe_payment_method_xid) { 'pm_abc123' } + let(:stripe_card_fingerprint) { 'card123' } let(:params) do { @@ -27,7 +30,10 @@ RSpec.describe Users::UpsertCreditCardValidationService, feature_category: :user credit_card_holder_name: holder_name, credit_card_type: network, credit_card_mask_number: last_digits, - zuora_payment_method_xid: zuora_payment_method_xid + zuora_payment_method_xid: zuora_payment_method_xid, + stripe_setup_intent_xid: stripe_setup_intent_xid, + stripe_payment_method_xid: stripe_payment_method_xid, + stripe_card_fingerprint: stripe_card_fingerprint } end @@ -52,7 +58,10 @@ RSpec.describe Users::UpsertCreditCardValidationService, feature_category: :user holder_name_hash: sha256(holder_name.downcase), last_digits_hash: sha256(last_digits), expiration_date_hash: sha256(expiration_date.to_s), - zuora_payment_method_xid: 'abc123' + zuora_payment_method_xid: 'abc123', + stripe_setup_intent_xid: 'seti_abc123', + stripe_payment_method_xid: 'pm_abc123', + stripe_card_fingerprint: 'card123' ) end end @@ -113,6 +122,28 @@ RSpec.describe Users::UpsertCreditCardValidationService, feature_category: :user end end + context 'when the stripe identifiers are missing' do + let(:stripe_setup_intent_xid) { nil } + let(:stripe_payment_method_xid) { nil } + let(:stripe_card_fingerprint) { nil } + + it 'successfully validates the credit card' do + service_result = service.execute + + expect(service_result).to be_success + expect(service_result.message).to eq(_('Credit card validation record saved')) + + user.reload + + expect(user.credit_card_validated_at).to be_present + expect(user.credit_card_validation).to have_attributes( + stripe_setup_intent_xid: nil, + stripe_payment_method_xid: nil, + stripe_card_fingerprint: nil + ) + end + end + context 'when the user_id does not exist' do let(:user_id) { non_existing_record_id } diff --git a/yarn.lock b/yarn.lock index 275b4dcb36d..baeece175b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1326,10 +1326,10 @@ stylelint-declaration-strict-value "1.10.4" stylelint-scss "6.0.0" -"@gitlab/svgs@3.102.0": - version "3.102.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.102.0.tgz#bf6750e858379b2d45ceb06026540704241df41a" - integrity sha512-EjjBTI3YVrMTG3ZBJSfIKxu7zqFkLLSPHrmQS+2xDgrLZhxFi61vpjp2+nGw1l8gzf0WXyBgqcMHF6YTTrTN4Q== +"@gitlab/svgs@3.103.0": + version "3.103.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.103.0.tgz#af61387481100eadef2bea8fe8605250311ac582" + integrity sha512-jVWCrRVRF6nw2A+Aowc0quXV2bdRPl2v08ElCPSestfdKjQ92tSlCrIsLB8GvdW5aI0eFsD1vJ1w2qkzZdpA4A== "@gitlab/ui@81.0.0": version "81.0.0"