summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-03-09 17:30:09 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-03-09 17:30:09 +0000
commit858d175c1527d650ea5d83e201777d0cf8ae84c9 (patch)
tree147344d5699ea84eda6f980867e4d2045e0f22b9
parentf6262510e6642bb91d112f1bf2ea70db8b7b772c (diff)
downloadgitlab-ce-858d175c1527d650ea5d83e201777d0cf8ae84c9.tar.gz
Add latest changes from gitlab-org/gitlab@15-9-stable-ee
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.checksum2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/issues/show/components/description.vue15
-rw-r--r--app/models/uploads/fog.rb13
-rw-r--r--config/gitlab.yml.example7
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--db/fixtures/development/24_forks.rb2
-rw-r--r--db/post_migrate/20221102231130_finalize_backfill_user_details_fields.rb24
-rw-r--r--db/post_migrate/20221102231131_remove_temp_index_for_user_details_fields.rb2
-rw-r--r--db/schema_migrations/202211022311301
-rw-r--r--doc/administration/geo/replication/troubleshooting.md5
-rw-r--r--doc/install/installation.md10
-rw-r--r--doc/update/background_migrations.md33
-rw-r--r--doc/update/index.md7
-rw-r--r--doc/update/upgrading_from_source.md8
-rw-r--r--doc/user/group/manage.md71
-rw-r--r--doc/user/project/issues/managing_issues.md8
-rw-r--r--doc/user/tasks.md10
-rw-r--r--lib/gitlab/database/migration_helpers.rb4
-rw-r--r--spec/frontend/issues/show/components/description_spec.js392
-rw-r--r--spec/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder_spec.rb3
-rw-r--r--spec/migrations/20221102231130_finalize_backfill_user_details_fields_spec.rb109
-rw-r--r--spec/models/uploads/fog_spec.rb87
-rw-r--r--spec/support/helpers/stub_object_storage.rb4
25 files changed, 559 insertions, 267 deletions
diff --git a/Gemfile b/Gemfile
index 9e4fd6cd3e9..ed0c5127f73 100644
--- a/Gemfile
+++ b/Gemfile
@@ -158,7 +158,7 @@ gem 'fog-rackspace', '~> 0.1.1'
# We may want to update this dependency if this is ever addressed upstream, e.g. via
# https://github.com/aliyun/aliyun-oss-ruby-sdk/pull/93
gem 'fog-aliyun', '~> 0.4'
-gem 'gitlab-fog-azure-rm', '~> 1.4.0', require: 'fog/azurerm'
+gem 'gitlab-fog-azure-rm', '~> 1.7.0', require: 'fog/azurerm'
# for Google storage
gem 'google-cloud-storage', '~> 1.44.0'
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 8e7967a81af..7813d319ee9 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -202,7 +202,7 @@
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
{"name":"gitlab-dangerfiles","version":"3.7.0","platform":"ruby","checksum":"35c5bc42e60c575ab5701192ca2384ab414b14c2963602b39e143b1aaeb7e54d"},
{"name":"gitlab-experiment","version":"0.7.1","platform":"ruby","checksum":"166dddb3aa83428bcaa93c35684ed01dc4d61f321fd2ae40b020806dc54a7824"},
-{"name":"gitlab-fog-azure-rm","version":"1.4.0","platform":"ruby","checksum":"af4163c32b028aa5208814a3f4765a5817d50527e6c61931f766bf18a2e0eb7e"},
+{"name":"gitlab-fog-azure-rm","version":"1.7.0","platform":"ruby","checksum":"969c67943c54ad4c259a6acd040493f13922fbdf2211bb4eca00e71505263dc2"},
{"name":"gitlab-labkit","version":"0.30.1","platform":"ruby","checksum":"bdedbd86014c83dfd6a50d20dbc1709697bba2bb9e3666383e5f28cbd312b113"},
{"name":"gitlab-license","version":"2.2.1","platform":"ruby","checksum":"39fcf6be8b2887df8afe01b5dcbae8d08b7c5d937ff56b0fb40484a8c4f02d30"},
{"name":"gitlab-mail_room","version":"0.0.9","platform":"ruby","checksum":"6700374b5c0aa9d9ad4e711aeb677f0b7d415a6d01d3baa699efab25349d851c"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 2e548c469ce..c988145616e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -577,7 +577,7 @@ GEM
gitlab-experiment (0.7.1)
activesupport (>= 3.0)
request_store (>= 1.0)
- gitlab-fog-azure-rm (1.4.0)
+ gitlab-fog-azure-rm (1.7.0)
azure-storage-blob (~> 2.0)
azure-storage-common (~> 2.0)
fog-core (= 2.1.0)
@@ -1678,7 +1678,7 @@ DEPENDENCIES
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 3.7.0)
gitlab-experiment (~> 0.7.1)
- gitlab-fog-azure-rm (~> 1.4.0)
+ gitlab-fog-azure-rm (~> 1.7.0)
gitlab-labkit (~> 0.30.1)
gitlab-license (~> 2.2.1)
gitlab-mail_room (~> 0.0.9)
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue
index 188a6f6b15e..bca895bf764 100644
--- a/app/assets/javascripts/issues/show/components/description.vue
+++ b/app/assets/javascripts/issues/show/components/description.vue
@@ -143,7 +143,7 @@ export default {
};
},
skip() {
- return !this.workItemId || !this.workItemsMvcEnabled;
+ return !this.workItemId;
},
},
workItemTypes: {
@@ -156,15 +156,9 @@ export default {
update(data) {
return data.workspace?.workItemTypes?.nodes;
},
- skip() {
- return !this.workItemsMvcEnabled;
- },
},
},
computed: {
- workItemsMvcEnabled() {
- return this.glFeatures.workItemsMvc;
- },
taskWorkItemType() {
return this.workItemTypes.find((type) => type.name === TASK_TYPE_NAME)?.id;
},
@@ -194,8 +188,7 @@ export default {
this.renderGFM();
this.updateTaskStatusText();
-
- if (this.workItemId && this.workItemsMvcEnabled) {
+ if (this.workItemId) {
const taskLink = this.$el.querySelector(
`.gfm-issue[data-issue="${getIdFromGraphQLId(this.workItemId)}"]`,
);
@@ -228,9 +221,7 @@ export default {
this.renderSortableLists();
- if (this.workItemsMvcEnabled) {
- this.renderTaskListItemActions();
- }
+ this.renderTaskListItemActions();
}
},
renderSortableLists() {
diff --git a/app/models/uploads/fog.rb b/app/models/uploads/fog.rb
index 5d57b644dbe..d2b8eab9f0d 100644
--- a/app/models/uploads/fog.rb
+++ b/app/models/uploads/fog.rb
@@ -21,7 +21,9 @@ module Uploads
private
def delete_object(key)
- connection.delete_object(bucket_name, key)
+ return unless available?
+
+ connection.delete_object(bucket_name, object_key(key))
# So far, only GoogleCloudStorage raises an exception when the file is not found.
# Other providers support idempotent requests and does not raise an error
@@ -35,11 +37,16 @@ module Uploads
end
def bucket_name
- return unless available?
-
object_store.remote_directory
end
+ def object_key(key)
+ # We allow administrators to create "sub buckets" by setting a prefix.
+ # This makes it possible to deploy GitLab with only one object storage
+ # bucket. This mirrors the implementation in app/uploaders/object_storage.rb.
+ File.join([object_store.bucket_prefix, key].compact)
+ end
+
def connection
return unless available?
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 5a9811c0e91..c89db25c347 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -628,11 +628,16 @@ production: &base
geo_secondary_registry_consistency_worker:
cron: "* * * * *"
- # GitLab Geo registry sync worker (for backfilling)
+ # GitLab Geo blob registry sync worker (for backfilling)
# NOTE: This will only take effect if Geo is enabled (secondary nodes only)
geo_registry_sync_worker:
cron: "*/1 * * * *"
+ # GitLab Geo repository registry sync worker (for backfilling)
+ # NOTE: This will only take effect if Geo is enabled (secondary nodes only)
+ geo_repository_registry_sync_worker:
+ cron: "*/1 * * * *"
+
# Elasticsearch bulk updater for incremental updates.
# NOTE: This will only take effect if elasticsearch is enabled.
elastic_index_bulk_cron_worker:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index b05fa6c1d8d..a1637d8c339 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -721,6 +721,9 @@ Gitlab.ee do
Settings.cron_jobs['geo_registry_sync_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['geo_registry_sync_worker']['cron'] ||= '*/1 * * * *'
Settings.cron_jobs['geo_registry_sync_worker']['job_class'] ||= 'Geo::RegistrySyncWorker'
+ Settings.cron_jobs['geo_repository_registry_sync_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['geo_repository_registry_sync_worker']['cron'] ||= '*/1 * * * *'
+ Settings.cron_jobs['geo_repository_registry_sync_worker']['job_class'] ||= 'Geo::RepositoryRegistrySyncWorker'
Settings.cron_jobs['geo_metrics_update_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['geo_metrics_update_worker']['cron'] ||= '*/1 * * * *'
Settings.cron_jobs['geo_metrics_update_worker']['job_class'] ||= 'Geo::MetricsUpdateWorker'
diff --git a/db/fixtures/development/24_forks.rb b/db/fixtures/development/24_forks.rb
index a3db84ab1b7..1476681f4d7 100644
--- a/db/fixtures/development/24_forks.rb
+++ b/db/fixtures/development/24_forks.rb
@@ -2,7 +2,7 @@ require './spec/support/sidekiq_middleware'
Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
- User.not_mass_generated.sample(10).each do |user|
+ User.humans.not_mass_generated.sample(10).each do |user|
source_project = Project.not_mass_generated.public_only.sample
##
diff --git a/db/post_migrate/20221102231130_finalize_backfill_user_details_fields.rb b/db/post_migrate/20221102231130_finalize_backfill_user_details_fields.rb
new file mode 100644
index 00000000000..a6b5bdd307e
--- /dev/null
+++ b/db/post_migrate/20221102231130_finalize_backfill_user_details_fields.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class FinalizeBackfillUserDetailsFields < Gitlab::Database::Migration[2.0]
+ BACKFILL_MIGRATION = 'BackfillUserDetailsFields'
+
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ # If the 20230116160904_remove_user_details_fields_from_user.rb migration already ran,
+ # finalizing this background migration will fail.
+ return unless column_exists?(:users, :linkedin)
+
+ ensure_batched_background_migration_is_finished(
+ job_class_name: BACKFILL_MIGRATION,
+ table_name: :users,
+ column_name: :id,
+ job_arguments: [],
+ finalize: true)
+ end
+
+ def down; end
+end
diff --git a/db/post_migrate/20221102231131_remove_temp_index_for_user_details_fields.rb b/db/post_migrate/20221102231131_remove_temp_index_for_user_details_fields.rb
index 340d1205ef7..c31513e00fc 100644
--- a/db/post_migrate/20221102231131_remove_temp_index_for_user_details_fields.rb
+++ b/db/post_migrate/20221102231131_remove_temp_index_for_user_details_fields.rb
@@ -1,13 +1,11 @@
# frozen_string_literal: true
class RemoveTempIndexForUserDetailsFields < Gitlab::Database::Migration[2.0]
- BACKFILL_MIGRATION = 'BackfillUserDetailsFields'
INDEX_NAME = 'tmp_idx_where_user_details_fields_filled'
disable_ddl_transaction!
def up
- finalize_background_migration BACKFILL_MIGRATION
remove_concurrent_index_by_name :users, INDEX_NAME
end
diff --git a/db/schema_migrations/20221102231130 b/db/schema_migrations/20221102231130
new file mode 100644
index 00000000000..ee01f627eef
--- /dev/null
+++ b/db/schema_migrations/20221102231130
@@ -0,0 +1 @@
+8678040a9fa8da1d455489db89e00084943d1dced6dd01cbf3517afd1a47bac5 \ No newline at end of file
diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md
index 403f8525d39..59a67fecfcd 100644
--- a/doc/administration/geo/replication/troubleshooting.md
+++ b/doc/administration/geo/replication/troubleshooting.md
@@ -1353,7 +1353,7 @@ If you have installed GitLab using the Linux package (Omnibus) and have configur
- `15.6.0`-`15.6.3`
- `15.7.0`-`15.7.1`
-This is due to [a bug introduced in the included version of cURL](https://github.com/curl/curl/issues/10122) shipped with Omnibus GitLab 15.4.6 and later. You are encouraged to upgrade to a later version where this has been [fixed](https://about.gitlab.com/releases/2023/01/09/security-release-gitlab-15-7-2-released/).
+This is due to [a bug introduced in the included version of cURL](https://github.com/curl/curl/issues/10122) shipped with Omnibus GitLab 15.4.6 and later. You are encouraged to upgrade to a later version where this has been [fixed](https://about.gitlab.com/releases/2023/01/09/security-release-gitlab-15-7-2-released/).
The bug causes all wildcard domains (`.example.com`) to be ignored except for the last on in the `no_proxy` environment variable list. Therefore, if for any reason you cannot upgrade to a newer version, you can work around the issue by moving your wildcard domain to the end of the list:
@@ -1363,12 +1363,13 @@ The bug causes all wildcard domains (`.example.com`) to be ignored except for th
gitaly['env'] = {
"no_proxy" => "sever.yourdomain.org, .yourdomain.com",
}
+ ```
1. Reconfigure GitLab:
```shell
sudo gitlab-ctl reconfigure
- ```
+ ```
You can have only one wildcard domain in the `no_proxy` list.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index be8667d5715..eda9c503e28 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -117,20 +117,14 @@ Install the required packages (needed to compile Ruby and native extensions to R
```shell
sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev \
libreadline-dev libncurses5-dev libffi-dev curl openssh-server libxml2-dev libxslt-dev \
- libcurl4-openssl-dev libicu-dev logrotate rsync python3-docutils pkg-config cmake runit-systemd
+ libcurl4-openssl-dev libicu-dev libkrb5-dev logrotate rsync python3-docutils pkg-config cmake \
+ runit-systemd
```
NOTE:
GitLab requires OpenSSL version 1.1. If your Linux distribution includes a different version of OpenSSL,
you might have to install 1.1 manually.
-If you want to use Kerberos for user authentication, install `libkrb5-dev`
-(if you don't know what Kerberos is, you can assume you don't need it):
-
-```shell
-sudo apt-get install libkrb5-dev
-```
-
### Git
From GitLab 13.6, we recommend you use the
diff --git a/doc/update/background_migrations.md b/doc/update/background_migrations.md
index 1f9ef9d430b..a55d2af8dd4 100644
--- a/doc/update/background_migrations.md
+++ b/doc/update/background_migrations.md
@@ -266,7 +266,7 @@ arguments until the status query returns no rows.
1. Run a reconfigure:
- ```plaintext
+ ```plaintext
sudo gitlab-ctl reconfigure
```
@@ -325,6 +325,37 @@ The results from the query can be plugged into the command:
sudo gitlab-rake gitlab:background_migrations:finalize[CopyColumnUsingBackgroundMigrationJob,events,id,'[["id"]\, ["id_convert_to_bigint"]]']
```
+#### Mark a batched migration finished
+
+There can be cases where the background migration fails: when jumping too many version upgrades,
+or backward-incompatible database schema changes. (For an example, see [issue 393216](https://gitlab.com/gitlab-org/gitlab/-/issues/393216)).
+Failed background migrations prevent further application upgrades.
+
+When the background migration is determined to be "safe" to skip, the migration can be manually marked finished:
+
+WARNING:
+Make sure you create a backup before proceeding.
+
+```ruby
+# Start the rails console
+
+connection = ApplicationRecord.connection # or Ci::ApplicationRecord.connection, depending on which DB was the migration scheduled
+
+Gitlab::Database::SharedModel.using_connection(connection) do
+ migration = Gitlab::Database::BackgroundMigration::BatchedMigration.find_for_configuration(
+ Gitlab::Database.gitlab_schemas_for_connection(connection),
+ 'BackfillUserDetailsFields',
+ :users,
+ :id,
+ []
+ )
+
+ # mark all jobs completed
+ migration.batched_jobs.update_all(status: Gitlab::Database::BackgroundMigration::BatchedJob.state_machine.states['succeeded'].value)
+ migration.update_attribute(:status, Gitlab::Database::BackgroundMigration::BatchedMigration.state_machine.states[:finished].value)
+end
+```
+
### The `BackfillNamespaceIdForNamespaceRoute` batched migration job fails
In GitLab 14.8, the `BackfillNamespaceIdForNamespaceRoute` batched background migration job
diff --git a/doc/update/index.md b/doc/update/index.md
index 4c909395f54..d08368663da 100644
--- a/doc/update/index.md
+++ b/doc/update/index.md
@@ -376,10 +376,10 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
- GitLab Runner 15.7.0 introduced a breaking change that impacts CI/CD jobs: [Correctly handle expansion of job file variables](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3613).
Previously, job-defined variables that referred to
[file type variables](../ci/variables/index.md#use-file-type-cicd-variables)
- were expanded to the value of the file variable (its content). This behavior did not
+ were expanded to the value of the file variable (its content). This behavior did not
respect the typical rules of shell variable expansion. There was also the potential
- that secrets or sensitive information could leak if the file variable and its
- contents printed. For example, if they were printed in an echo output. For more information,
+ that secrets or sensitive information could leak if the file variable and its
+ contents printed. For example, if they were printed in an echo output. For more information,
see [Understanding the file type variable expansion change in GitLab 15.7](https://about.gitlab.com/blog/2023/02/13/impact-of-the-file-type-variable-change-15-7/).
- Geo: [Container registry push events are rejected](https://gitlab.com/gitlab-org/gitlab/-/issues/386389) by the `/api/v4/container_registry_event/events` endpoint resulting in Geo secondary sites not being aware of updates to container registry images and subsequently not replicating the updates. Secondary sites may contain out of date container images after a failover as a consequence. This impacts versions 15.6.0 - 15.6.6 and 15.7.0 - 15.7.2. If you're using Geo with container repositories, you are advised to upgrade to GitLab 15.6.7, 15.7.3, or 15.8.0 which contain a fix for this issue and avoid potential data loss after a failover.
- Due to [a bug introduced in GitLab 15.4](https://gitlab.com/gitlab-org/gitlab/-/issues/390155), if one or more Git repositories in Gitaly Cluster is [unavailable](../administration/gitaly/recovery.md#unavailable-repositories), then [Repository checks](../administration/repository_checks.md#repository-checks) and [Geo replication and verification](../administration/geo/index.md) stop running for all project or project wiki repositories in the affected Gitaly Cluster. The bug was fixed by [reverting the change in GitLab 15.9.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110823). Before upgrading to this version, check if you have any "unavailable" repositories. See [the bug issue](https://gitlab.com/gitlab-org/gitlab/-/issues/390155) for more information.
@@ -524,6 +524,7 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
### 15.4.6
+- Due to a [bug introduced in curl in GitLab 15.4.6](https://github.com/curl/curl/issues/10122), the [`no_proxy` environment variable may not work properly](../administration/geo/replication/troubleshooting.md#secondary-site-returns-received-http-code-403-from-proxy-after-connect). Either downgrade to GitLab 15.4.5, or upgrade to GitLab 15.5.7 or a later version.
- Due to [a bug introduced in GitLab 15.4](https://gitlab.com/gitlab-org/gitlab/-/issues/390155), if one or more Git repositories in Gitaly Cluster is [unavailable](../administration/gitaly/recovery.md#unavailable-repositories), then [Repository checks](../administration/repository_checks.md#repository-checks) and [Geo replication and verification](../administration/geo/index.md) stop running for all project or project wiki repositories in the affected Gitaly Cluster. The bug was fixed by [reverting the change in GitLab 15.9.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110823). Before upgrading to this version, check if you have any "unavailable" repositories. See [the bug issue](https://gitlab.com/gitlab-org/gitlab/-/issues/390155) for more information.
### 15.4.5
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index e4c2f0003af..b5ce0e74100 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -422,6 +422,14 @@ Example:
Additional instructions here.
-->
+### 15.9.0
+
+With the addition of `gitlab-sshd` the Kerberos headers are needed to build GitLab Shell.
+
+```shell
+sudo apt install libkrb5-dev
+```
+
### 15.0.0
Support for more than one database has been added to GitLab. [As part of this](https://gitlab.com/gitlab-org/gitlab/-/issues/338182),
diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md
index fc72b81d74c..d21dbe357da 100644
--- a/doc/user/group/manage.md
+++ b/doc/user/group/manage.md
@@ -549,6 +549,77 @@ To enable group file templates:
1. Choose a project to act as the template repository.
1. Select **Save changes**.
+## Group merge checks settings **(PREMIUM)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/372040) in GitLab 15.9 [with a flag](../../administration/feature_flags.md) name `support_group_level_merge_checks_setting`. Disabled by default.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to
+[enable the feature flag](../../administration/feature_flags.md) named `support_group_level_merge_checks_setting`. On GitLab.com, this feature is not
+available.
+
+Group owners can set up merge request checks on a top-level group, which apply to all subgroups and projects.
+
+If the settings are inherited by a subgroup or project, they cannot be changed in the subgroup or project
+that inherited them.
+
+### Require a successful pipeline for merge
+
+You can configure all child projects in your group to require a complete and successful pipeline before
+merge.
+
+See also [the project-level setting](../project/merge_requests/merge_when_pipeline_succeeds.md#require-a-successful-pipeline-for-merge).
+
+Prerequisites:
+
+- You must be the owner of the group.
+
+To enable this setting:
+
+1. On the top bar, select **Main menu > Groups** and find your group.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Merge requests**.
+1. Under **Merge checks**, select **Pipelines must succeed**.
+ This setting also prevents merge requests from being merged if there is no pipeline.
+1. Select **Save changes**.
+
+#### Allow merge after skipped pipelines
+
+You can configure [skipped pipelines](../../ci/pipelines/index.md#skip-a-pipeline) from preventing merge requests from being merged.
+
+See also [the project-level setting](../project/merge_requests/merge_when_pipeline_succeeds.md#allow-merge-after-skipped-pipelines).
+
+Prerequisite:
+
+- You must be the owner of the group.
+
+To change this behavior:
+
+1. On the top bar, select **Main menu > Groups** and find your group.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Merge requests**.
+1. Under **Merge checks**:
+ - Select **Pipelines must succeed**.
+ - Select **Skipped pipelines are considered successful**.
+1. Select **Save changes**.
+
+### Prevent merge unless all threads are resolved
+
+You can prevent merge requests from being merged until all threads are resolved. When this setting is enabled, for all child projects in your group, the
+**Unresolved threads** count in a merge request is shown in orange when at least one thread remains unresolved.
+
+Prerequisite:
+
+- You must be the owner of the group.
+
+To enable this setting:
+
+1. On the top bar, select **Main menu > Groups** and find your group.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Merge requests**.
+1. Under **Merge checks**, select **All threads must be resolved**.
+1. Select **Save changes**.
+
## Group merge request approval settings **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285458) in GitLab 13.9. [Deployed behind the `group_merge_request_approval_settings_feature_flag` flag](../../administration/feature_flags.md), disabled by default.
diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md
index 953d08ea903..c16074ea1d8 100644
--- a/doc/user/project/issues/managing_issues.md
+++ b/doc/user/project/issues/managing_issues.md
@@ -24,13 +24,7 @@ To edit an issue:
### Remove a task list item
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/377307) in GitLab 15.9 [with a flag](../../../administration/feature_flags.md) named `work_items_mvc`. Disabled by default.
-
-FLAG:
-On self-managed GitLab, by default this feature is not available.
-To make it available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `work_items_mvc`.
-On GitLab.com, this feature is not available.
-The feature is not ready for production use.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/377307) in GitLab 15.9.
Prerequisites:
diff --git a/doc/user/tasks.md b/doc/user/tasks.md
index 42a3975a9d2..0fc4c7571ab 100644
--- a/doc/user/tasks.md
+++ b/doc/user/tasks.md
@@ -58,15 +58,9 @@ To create a task:
1. Enter the task title.
1. Select **Create task**.
-### Create a task from a task list item
+### From a task list item
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/377307) in GitLab 15.9 [with a flag](../administration/feature_flags.md) named `work_items_mvc`. Disabled by default.
-
-FLAG:
-On self-managed GitLab, by default this feature is not available.
-To make it available, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `work_items_mvc`.
-On GitLab.com, this feature is not available.
-The feature is not ready for production use.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/377307) in GitLab 15.9.
Prerequisites:
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 9c1cb8e352c..9b041c18da4 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -367,12 +367,12 @@ module Gitlab
def foreign_key_exists?(source, target = nil, **options)
# This if block is necessary because foreign_key_exists? is called in down migrations that may execute before
- # the postgres_foreign_keys view had necessary columns added, or even before the view existed.
+ # the postgres_foreign_keys view had necessary columns added.
# In that case, we revert to the previous behavior of this method.
# The behavior in the if block has a bug: it always returns false if the fk being checked has multiple columns.
# This can be removed after init_schema.rb passes 20221122210711_add_columns_to_postgres_foreign_keys.rb
# Tracking issue: https://gitlab.com/gitlab-org/gitlab/-/issues/386796
- if ActiveRecord::Migrator.current_version < 20221122210711
+ unless connection.column_exists?('postgres_foreign_keys', 'constrained_table_name')
return foreign_keys(source).any? do |foreign_key|
tables_match?(target.to_s, foreign_key.to_table.to_s) &&
options_match?(foreign_key.options, options)
diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js
index 3f4513e6bfa..da51372dd3d 100644
--- a/spec/frontend/issues/show/components/description_spec.js
+++ b/spec/frontend/issues/show/components/description_spec.js
@@ -310,69 +310,58 @@ describe('Description component', () => {
});
});
- describe('with work_items_mvc feature flag enabled', () => {
- describe('empty description', () => {
- beforeEach(() => {
- createComponent({
- props: {
- descriptionHtml: '',
- },
- provide: {
- glFeatures: {
- workItemsMvc: true,
- },
- },
- });
- return nextTick();
+ describe('empty description', () => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ descriptionHtml: '',
+ },
});
+ return nextTick();
+ });
- it('renders without error', () => {
- expect(findTaskActionButtons()).toHaveLength(0);
- });
+ it('renders without error', () => {
+ expect(findTaskActionButtons()).toHaveLength(0);
});
+ });
- describe('description with checkboxes', () => {
- beforeEach(() => {
- createComponent({
- props: {
- descriptionHtml: descriptionHtmlWithCheckboxes,
- },
- provide: {
- glFeatures: {
- workItemsMvc: true,
- },
- },
- });
- return nextTick();
+ describe('description with checkboxes', () => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ descriptionHtml: descriptionHtmlWithCheckboxes,
+ },
});
+ return nextTick();
+ });
- it('renders a list of hidden buttons corresponding to checkboxes in description HTML', () => {
- expect(findTaskActionButtons()).toHaveLength(3);
- });
+ it('renders a list of hidden buttons corresponding to checkboxes in description HTML', () => {
+ expect(findTaskActionButtons()).toHaveLength(3);
+ });
- it('does not show a modal by default', () => {
- expect(findModal().exists()).toBe(false);
- });
+ it('does not show a modal by default', () => {
+ expect(findModal().exists()).toBe(false);
+ });
- it('shows toast after delete success', async () => {
- const newDesc = 'description';
- findWorkItemDetailModal().vm.$emit('workItemDeleted', newDesc);
+ it('shows toast after delete success', async () => {
+ const newDesc = 'description';
+ findWorkItemDetailModal().vm.$emit('workItemDeleted', newDesc);
- expect(wrapper.emitted('updateDescription')).toEqual([[newDesc]]);
- expect($toast.show).toHaveBeenCalledWith('Task deleted');
- });
+ expect(wrapper.emitted('updateDescription')).toEqual([[newDesc]]);
+ expect($toast.show).toHaveBeenCalledWith('Task deleted');
});
+ });
- describe('task list item actions', () => {
- describe('converting the task list item to a task', () => {
- describe('when successful', () => {
- let createWorkItemMutationHandler;
+ describe('task list item actions', () => {
+ describe('converting the task list item to a task', () => {
+ describe('when successful', () => {
+ let createWorkItemMutationHandler;
- beforeEach(async () => {
- createWorkItemMutationHandler = jest
- .fn()
- .mockResolvedValue(createWorkItemMutationResponse);
- const descriptionText = `Tasks
+ beforeEach(async () => {
+ createWorkItemMutationHandler = jest
+ .fn()
+ .mockResolvedValue(createWorkItemMutationResponse);
+ const descriptionText = `Tasks
1. [ ] item 1
1. [ ] item 2
@@ -381,218 +370,207 @@ describe('Description component', () => {
1. [ ] item 3
1. [ ] item 4;`;
- createComponent({
- props: { descriptionText },
- provide: { glFeatures: { workItemsMvc: true } },
- createWorkItemMutationHandler,
- });
- await waitForPromises();
-
- eventHub.$emit('convert-task-list-item', '4:4-8:19');
- await waitForPromises();
+ createComponent({
+ props: { descriptionText },
+ createWorkItemMutationHandler,
});
+ await waitForPromises();
- it('emits an event to update the description with the deleted task list item omitted', () => {
- const newDescriptionText = `Tasks
+ eventHub.$emit('convert-task-list-item', '4:4-8:19');
+ await waitForPromises();
+ });
+
+ it('emits an event to update the description with the deleted task list item omitted', () => {
+ const newDescriptionText = `Tasks
1. [ ] item 1
1. [ ] item 3
1. [ ] item 4;`;
- expect(wrapper.emitted('saveDescription')).toEqual([[newDescriptionText]]);
- });
+ expect(wrapper.emitted('saveDescription')).toEqual([[newDescriptionText]]);
+ });
- it('calls a mutation to create a task', () => {
- const {
+ it('calls a mutation to create a task', () => {
+ const {
+ confidential,
+ iteration,
+ milestone,
+ } = issueDetailsResponse.data.workspace.issuable;
+ expect(createWorkItemMutationHandler).toHaveBeenCalledWith({
+ input: {
confidential,
- iteration,
- milestone,
- } = issueDetailsResponse.data.workspace.issuable;
- expect(createWorkItemMutationHandler).toHaveBeenCalledWith({
- input: {
- confidential,
- description: '\nparagraph text\n',
- hierarchyWidget: {
- parentId: 'gid://gitlab/WorkItem/1',
- },
- iterationWidget: {
- iterationId: IS_EE ? iteration.id : null,
- },
- milestoneWidget: {
- milestoneId: milestone.id,
- },
- projectPath: 'gitlab-org/gitlab-test',
- title: 'item 2',
- workItemTypeId: 'gid://gitlab/WorkItems::Type/3',
+ description: '\nparagraph text\n',
+ hierarchyWidget: {
+ parentId: 'gid://gitlab/WorkItem/1',
+ },
+ iterationWidget: {
+ iterationId: IS_EE ? iteration.id : null,
+ },
+ milestoneWidget: {
+ milestoneId: milestone.id,
},
- });
+ projectPath: 'gitlab-org/gitlab-test',
+ title: 'item 2',
+ workItemTypeId: 'gid://gitlab/WorkItems::Type/3',
+ },
});
+ });
- it('shows a toast to confirm the creation of the task', () => {
- expect($toast.show).toHaveBeenCalledWith('Converted to task', expect.any(Object));
- });
+ it('shows a toast to confirm the creation of the task', () => {
+ expect($toast.show).toHaveBeenCalledWith('Converted to task', expect.any(Object));
});
+ });
- describe('when unsuccessful', () => {
- beforeEach(async () => {
- createComponent({
- props: { descriptionText: 'description' },
- provide: { glFeatures: { workItemsMvc: true } },
- createWorkItemMutationHandler: jest
- .fn()
- .mockResolvedValue(createWorkItemMutationErrorResponse),
- });
- await waitForPromises();
-
- eventHub.$emit('convert-task-list-item', '1:1-1:11');
- await waitForPromises();
+ describe('when unsuccessful', () => {
+ beforeEach(async () => {
+ createComponent({
+ props: { descriptionText: 'description' },
+ createWorkItemMutationHandler: jest
+ .fn()
+ .mockResolvedValue(createWorkItemMutationErrorResponse),
});
+ await waitForPromises();
- it('shows an alert with an error message', () => {
- expect(createAlert).toHaveBeenCalledWith({
- message: 'Something went wrong when creating task. Please try again.',
- error: new Error('an error'),
- captureError: true,
- });
+ eventHub.$emit('convert-task-list-item', '1:1-1:11');
+ await waitForPromises();
+ });
+
+ it('shows an alert with an error message', () => {
+ expect(createAlert).toHaveBeenCalledWith({
+ message: 'Something went wrong when creating task. Please try again.',
+ error: new Error('an error'),
+ captureError: true,
});
});
});
+ });
- describe('deleting the task list item', () => {
- it('emits an event to update the description with the deleted task list item', () => {
- const descriptionText = `Tasks
+ describe('deleting the task list item', () => {
+ it('emits an event to update the description with the deleted task list item', () => {
+ const descriptionText = `Tasks
1. [ ] item 1
1. [ ] item 2
1. [ ] item 3
1. [ ] item 4;`;
- const newDescriptionText = `Tasks
+ const newDescriptionText = `Tasks
1. [ ] item 1
1. [ ] item 3
1. [ ] item 4;`;
- createComponent({
- props: { descriptionText },
- provide: { glFeatures: { workItemsMvc: true } },
- });
+ createComponent({
+ props: { descriptionText },
+ });
- eventHub.$emit('delete-task-list-item', '4:4-5:19');
+ eventHub.$emit('delete-task-list-item', '4:4-5:19');
- expect(wrapper.emitted('saveDescription')).toEqual([[newDescriptionText]]);
- });
+ expect(wrapper.emitted('saveDescription')).toEqual([[newDescriptionText]]);
});
});
+ });
- describe('work items detail', () => {
- describe('when opening and closing', () => {
- beforeEach(() => {
- createComponent({
- props: {
- descriptionHtml: descriptionHtmlWithTask,
- },
- provide: {
- glFeatures: { workItemsMvc: true },
- },
- });
- return nextTick();
+ describe('work items detail', () => {
+ describe('when opening and closing', () => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ descriptionHtml: descriptionHtmlWithTask,
+ },
});
+ return nextTick();
+ });
- it('opens when task button is clicked', async () => {
- await findTaskLink().trigger('click');
+ it('opens when task button is clicked', async () => {
+ await findTaskLink().trigger('click');
- expect(showDetailsModal).toHaveBeenCalled();
- expect(updateHistory).toHaveBeenCalledWith({
- url: `${TEST_HOST}/?work_item_id=2`,
- replace: true,
- });
+ expect(showDetailsModal).toHaveBeenCalled();
+ expect(updateHistory).toHaveBeenCalledWith({
+ url: `${TEST_HOST}/?work_item_id=2`,
+ replace: true,
});
+ });
- it('closes from an open state', async () => {
- await findTaskLink().trigger('click');
+ it('closes from an open state', async () => {
+ await findTaskLink().trigger('click');
- findWorkItemDetailModal().vm.$emit('close');
- await nextTick();
+ findWorkItemDetailModal().vm.$emit('close');
+ await nextTick();
- expect(updateHistory).toHaveBeenLastCalledWith({
- url: `${TEST_HOST}/`,
- replace: true,
- });
+ expect(updateHistory).toHaveBeenLastCalledWith({
+ url: `${TEST_HOST}/`,
+ replace: true,
});
+ });
- it('tracks when opened', async () => {
- const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
-
- await findTaskLink().trigger('click');
+ it('tracks when opened', async () => {
+ const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- expect(trackingSpy).toHaveBeenCalledWith(
- TRACKING_CATEGORY_SHOW,
- 'viewed_work_item_from_modal',
- {
- category: TRACKING_CATEGORY_SHOW,
- label: 'work_item_view',
- property: 'type_task',
- },
- );
- });
- });
+ await findTaskLink().trigger('click');
- describe('when url query `work_item_id` exists', () => {
- it.each`
- behavior | workItemId | modalOpened
- ${'opens'} | ${'2'} | ${1}
- ${'does not open'} | ${'123'} | ${0}
- ${'does not open'} | ${'123e'} | ${0}
- ${'does not open'} | ${'12e3'} | ${0}
- ${'does not open'} | ${'1e23'} | ${0}
- ${'does not open'} | ${'x'} | ${0}
- ${'does not open'} | ${'undefined'} | ${0}
- `(
- '$behavior when url contains `work_item_id=$workItemId`',
- async ({ workItemId, modalOpened }) => {
- setWindowLocation(`?work_item_id=${workItemId}`);
-
- createComponent({
- props: { descriptionHtml: descriptionHtmlWithTask },
- provide: { glFeatures: { workItemsMvc: true } },
- });
-
- expect(showDetailsModal).toHaveBeenCalledTimes(modalOpened);
+ expect(trackingSpy).toHaveBeenCalledWith(
+ TRACKING_CATEGORY_SHOW,
+ 'viewed_work_item_from_modal',
+ {
+ category: TRACKING_CATEGORY_SHOW,
+ label: 'work_item_view',
+ property: 'type_task',
},
);
});
});
- describe('when hovering task links', () => {
- beforeEach(() => {
- createComponent({
- props: {
- descriptionHtml: descriptionHtmlWithTask,
- },
- provide: {
- glFeatures: { workItemsMvc: true },
- },
- });
- return nextTick();
- });
+ describe('when url query `work_item_id` exists', () => {
+ it.each`
+ behavior | workItemId | modalOpened
+ ${'opens'} | ${'2'} | ${1}
+ ${'does not open'} | ${'123'} | ${0}
+ ${'does not open'} | ${'123e'} | ${0}
+ ${'does not open'} | ${'12e3'} | ${0}
+ ${'does not open'} | ${'1e23'} | ${0}
+ ${'does not open'} | ${'x'} | ${0}
+ ${'does not open'} | ${'undefined'} | ${0}
+ `(
+ '$behavior when url contains `work_item_id=$workItemId`',
+ async ({ workItemId, modalOpened }) => {
+ setWindowLocation(`?work_item_id=${workItemId}`);
- it('prefetches work item detail after work item link is hovered for 150ms', async () => {
- await findTaskLink().trigger('mouseover');
- jest.advanceTimersByTime(150);
- await waitForPromises();
+ createComponent({
+ props: { descriptionHtml: descriptionHtmlWithTask },
+ });
- expect(queryHandler).toHaveBeenCalledWith({
- id: 'gid://gitlab/WorkItem/2',
- });
+ expect(showDetailsModal).toHaveBeenCalledTimes(modalOpened);
+ },
+ );
+ });
+ });
+
+ describe('when hovering task links', () => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ descriptionHtml: descriptionHtmlWithTask,
+ },
});
+ return nextTick();
+ });
- it('does not work item detail after work item link is hovered for less than 150ms', async () => {
- await findTaskLink().trigger('mouseover');
- await findTaskLink().trigger('mouseout');
- jest.advanceTimersByTime(150);
- await waitForPromises();
+ it('prefetches work item detail after work item link is hovered for 150ms', async () => {
+ await findTaskLink().trigger('mouseover');
+ jest.advanceTimersByTime(150);
+ await waitForPromises();
- expect(queryHandler).not.toHaveBeenCalled();
+ expect(queryHandler).toHaveBeenCalledWith({
+ id: 'gid://gitlab/WorkItem/2',
});
});
+
+ it('does not work item detail after work item link is hovered for less than 150ms', async () => {
+ await findTaskLink().trigger('mouseover');
+ await findTaskLink().trigger('mouseout');
+ jest.advanceTimersByTime(150);
+ await waitForPromises();
+
+ expect(queryHandler).not.toHaveBeenCalled();
+ });
});
});
diff --git a/spec/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder_spec.rb b/spec/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder_spec.rb
index 2862bcc9719..a15dbccc80c 100644
--- a/spec/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder_spec.rb
+++ b/spec/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder_spec.rb
@@ -28,7 +28,8 @@ RSpec.describe ::Gitlab::Seeders::Ci::Runner::RunnerFleetPipelineSeeder, feature
context 'with job_count specified' do
let(:job_count) { 20 }
- it 'creates expected jobs', :aggregate_failures do
+ it 'creates expected jobs', :aggregate_failures,
+ quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/394721' do
expect { seeder.seed }.to change { Ci::Build.count }.by(job_count)
.and change { Ci::Pipeline.count }.by(4)
diff --git a/spec/migrations/20221102231130_finalize_backfill_user_details_fields_spec.rb b/spec/migrations/20221102231130_finalize_backfill_user_details_fields_spec.rb
new file mode 100644
index 00000000000..37bff128edd
--- /dev/null
+++ b/spec/migrations/20221102231130_finalize_backfill_user_details_fields_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe FinalizeBackfillUserDetailsFields, :migration, feature_category: :user_management do
+ let(:batched_migrations) { table(:batched_background_migrations) }
+ let(:batch_failed_status) { 2 }
+ let(:batch_finalized_status) { 3 }
+
+ let!(:migration) { described_class::BACKFILL_MIGRATION }
+
+ describe '#up' do
+ shared_examples 'finalizes the migration' do
+ it 'finalizes the migration' do
+ expect do
+ migrate!
+
+ migration_record.reload
+ failed_job.reload
+ end.to change { migration_record.status }.from(migration_record.status).to(3).and(
+ change { failed_job.status }.from(batch_failed_status).to(batch_finalized_status)
+ )
+ end
+ end
+
+ context 'when migration is missing' do
+ it 'warns migration not found' do
+ expect(Gitlab::AppLogger)
+ .to receive(:warn).with(/Could not find batched background migration for the given configuration:/)
+
+ migrate!
+ end
+ end
+
+ context 'with migration present' do
+ let!(:migration_record) do
+ batched_migrations.create!(
+ job_class_name: migration,
+ table_name: :users,
+ column_name: :id,
+ job_arguments: [],
+ interval: 2.minutes,
+ min_value: 1,
+ max_value: 2,
+ batch_size: 1000,
+ sub_batch_size: 500,
+ max_batch_size: 5000,
+ gitlab_schema: :gitlab_main,
+ status: 3 # finished
+ )
+ end
+
+ context 'when migration finished successfully' do
+ it 'does not raise exception' do
+ expect { migrate! }.not_to raise_error
+ end
+ end
+
+ context 'when users.linkedin column has already been dropped' do
+ before do
+ table(:users).create!(id: 1, email: 'author@example.com', username: 'author', projects_limit: 10)
+ ActiveRecord::Base.connection.execute("ALTER TABLE users DROP COLUMN linkedin")
+ migration_record.update_column(:status, 1)
+ end
+
+ after do
+ ActiveRecord::Base.connection.execute("ALTER TABLE users ADD COLUMN linkedin text DEFAULT '' NOT NULL")
+ end
+
+ it 'does not raise exception' do
+ expect { migrate! }.not_to raise_error
+ end
+ end
+
+ context 'with different migration statuses', :redis do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:status, :description) do
+ 0 | 'paused'
+ 1 | 'active'
+ 4 | 'failed'
+ 5 | 'finalizing'
+ end
+
+ with_them do
+ let!(:failed_job) do
+ table(:batched_background_migration_jobs).create!(
+ batched_background_migration_id: migration_record.id,
+ status: batch_failed_status,
+ min_value: 1,
+ max_value: 10,
+ attempts: 2,
+ batch_size: 100,
+ sub_batch_size: 10
+ )
+ end
+
+ before do
+ migration_record.update!(status: status)
+ end
+
+ it_behaves_like 'finalizes the migration'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/uploads/fog_spec.rb b/spec/models/uploads/fog_spec.rb
index 1ffe7c6c43b..a1b0bcf95e0 100644
--- a/spec/models/uploads/fog_spec.rb
+++ b/spec/models/uploads/fog_spec.rb
@@ -3,10 +3,21 @@
require 'spec_helper'
RSpec.describe Uploads::Fog do
+ let(:credentials) do
+ {
+ provider: "AWS",
+ aws_access_key_id: "AWS_ACCESS_KEY_ID",
+ aws_secret_access_key: "AWS_SECRET_ACCESS_KEY",
+ region: "eu-central-1"
+ }
+ end
+
+ let(:bucket_prefix) { nil }
let(:data_store) { described_class.new }
+ let(:config) { { connection: credentials, bucket_prefix: bucket_prefix, remote_directory: 'uploads' } }
before do
- stub_uploads_object_storage(FileUploader)
+ stub_uploads_object_storage(FileUploader, config: config)
end
describe '#available?' do
@@ -18,7 +29,7 @@ RSpec.describe Uploads::Fog do
context 'when object storage is disabled' do
before do
- stub_uploads_object_storage(FileUploader, enabled: false)
+ stub_uploads_object_storage(FileUploader, config: config, enabled: false)
end
it { is_expected.to be_falsy }
@@ -28,6 +39,60 @@ RSpec.describe Uploads::Fog do
context 'model with uploads' do
let(:project) { create(:project) }
let(:relation) { project.uploads }
+ let(:connection) { ::Fog::Storage.new(credentials) }
+ let(:paths) { relation.pluck(:path) }
+
+ # Only fog-aws simulates mocking of deleting an object properly.
+ # We'll just test that the various providers implement the require methods.
+ describe 'Fog provider acceptance tests' do
+ let!(:uploads) { create_list(:upload, 2, :with_file, :issuable_upload, model: project) }
+
+ shared_examples 'Fog provider' do
+ describe '#get_object' do
+ it 'returns a Hash with a body' do
+ expect(connection.get_object('uploads', paths.first)[:body]).not_to be_nil
+ end
+ end
+
+ describe '#delete_object' do
+ it 'returns true' do
+ expect(connection.delete_object('uploads', paths.first)).to be_truthy
+ end
+ end
+ end
+
+ before do
+ uploads.each { |upload| upload.retrieve_uploader.migrate!(2) }
+ end
+
+ context 'with AWS provider' do
+ it_behaves_like 'Fog provider'
+ end
+
+ context 'with Google provider' do
+ let(:credentials) do
+ {
+ provider: "Google",
+ google_storage_access_key_id: 'ACCESS_KEY_ID',
+ google_storage_secret_access_key: 'SECRET_ACCESS_KEY'
+ }
+ end
+
+ it_behaves_like 'Fog provider'
+ end
+
+ context 'with AzureRM provider' do
+ let(:credentials) do
+ {
+ provider: 'AzureRM',
+ azure_storage_account_name: 'test-access-id',
+ azure_storage_access_key: 'secret'
+ }
+ end
+
+ it_behaves_like 'Fog provider'
+ end
+ end
describe '#keys' do
let!(:uploads) { create_list(:upload, 2, :object_storage, uploader: FileUploader, model: project) }
@@ -40,7 +105,7 @@ RSpec.describe Uploads::Fog do
end
describe '#delete_keys' do
- let(:connection) { ::Fog::Storage.new(FileUploader.object_store_credentials) }
+ let(:connection) { ::Fog::Storage.new(credentials) }
let(:keys) { data_store.keys(relation) }
let(:paths) { relation.pluck(:path) }
let!(:uploads) { create_list(:upload, 2, :with_file, :issuable_upload, model: project) }
@@ -63,6 +128,22 @@ RSpec.describe Uploads::Fog do
end
end
+ context 'with bucket prefix' do
+ let(:bucket_prefix) { 'test-prefix' }
+
+ it 'deletes multiple data' do
+ paths.each do |path|
+ expect(connection.get_object('uploads', File.join(bucket_prefix, path))[:body]).not_to be_nil
+ end
+
+ subject
+
+ paths.each do |path|
+ expect { connection.get_object('uploads', File.join(bucket_prefix, path))[:body] }.to raise_error(Excon::Error::NotFound)
+ end
+ end
+ end
+
context 'when one of keys is missing' do
let(:keys) { ['unknown'] + super() }
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index c163ce1d880..6b633856228 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -15,7 +15,7 @@ module StubObjectStorage
direct_upload: false,
cdn: {}
)
-
+ old_config = Settingslogic.new(config.deep_stringify_keys)
new_config = config.to_h.deep_symbolize_keys.merge({
enabled: enabled,
proxy_download: proxy_download,
@@ -37,7 +37,7 @@ module StubObjectStorage
return unless enabled
stub_object_storage(connection_params: uploader.object_store_credentials,
- remote_directory: config.remote_directory)
+ remote_directory: old_config.remote_directory)
end
def stub_object_storage(connection_params:, remote_directory:)