Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8308674afc
commit
2fdee6d838
|
|
@ -293,7 +293,7 @@
|
|||
- name: postgres:12
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
- name: redis:6.0-alpine
|
||||
- name: elasticsearch:8.4.1
|
||||
- name: elasticsearch:8.5.2
|
||||
variables:
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
PG_VERSION: "12"
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
---
|
||||
# Cop supports --autocorrect.
|
||||
RSpec/EmptyLineAfterExampleGroup:
|
||||
Exclude:
|
||||
- 'ee/spec/controllers/groups/clusters_controller_spec.rb'
|
||||
- 'ee/spec/features/projects/feature_flags/user_sees_feature_flag_list_spec.rb'
|
||||
- 'ee/spec/features/security/group/private_access_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/vulnerabilities/container_scanning_vulnerability_spec.rb'
|
||||
- 'ee/spec/services/ee/gpg_keys/create_service_spec.rb'
|
||||
- 'ee/spec/services/ee/issues/create_from_vulnerability_data_service_spec.rb'
|
||||
- 'ee/spec/services/vulnerabilities/confirm_service_spec.rb'
|
||||
- 'ee/spec/services/vulnerabilities/dismiss_service_spec.rb'
|
||||
- 'ee/spec/services/vulnerabilities/resolve_service_spec.rb'
|
||||
- 'ee/spec/services/vulnerabilities/revert_to_detected_service_spec.rb'
|
||||
- 'ee/spec/services/vulnerability_issue_links/create_service_spec.rb'
|
||||
- 'ee/spec/services/vulnerability_issue_links/delete_service_spec.rb'
|
||||
- 'spec/controllers/explore/projects_controller_spec.rb'
|
||||
- 'spec/controllers/projects/notes_controller_spec.rb'
|
||||
- 'spec/factories/projects/ci_feature_usages.rb'
|
||||
- 'spec/features/security/group/internal_access_spec.rb'
|
||||
- 'spec/features/security/group/private_access_spec.rb'
|
||||
- 'spec/features/security/group/public_access_spec.rb'
|
||||
- 'spec/helpers/blob_helper_spec.rb'
|
||||
- 'spec/helpers/git_helper_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/issues_pipeline_spec.rb'
|
||||
- 'spec/lib/gitlab/blob_helper_spec.rb'
|
||||
- 'spec/lib/gitlab/file_type_detection_spec.rb'
|
||||
- 'spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb'
|
||||
- 'spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb'
|
||||
- 'spec/models/note_spec.rb'
|
||||
- 'spec/models/project_feature_spec.rb'
|
||||
- 'spec/models/user_spec.rb'
|
||||
- 'spec/models/zoom_meeting_spec.rb'
|
||||
- 'spec/requests/api/projects_spec.rb'
|
||||
- 'spec/routing/project_routing_spec.rb'
|
||||
|
|
@ -16,3 +16,5 @@ module PreferredLanguageSwitcher
|
|||
Gitlab::CurrentSettings.default_preferred_language
|
||||
end
|
||||
end
|
||||
|
||||
PreferredLanguageSwitcher.prepend_mod
|
||||
|
|
|
|||
|
|
@ -6,6 +6,13 @@ class ProjectExportJob < ApplicationRecord
|
|||
|
||||
validates :project, :jid, :status, presence: true
|
||||
|
||||
STATUS = {
|
||||
queued: 0,
|
||||
started: 1,
|
||||
finished: 2,
|
||||
failed: 3
|
||||
}.freeze
|
||||
|
||||
state_machine :status, initial: :queued do
|
||||
event :start do
|
||||
transition [:queued] => :started
|
||||
|
|
@ -19,9 +26,9 @@ class ProjectExportJob < ApplicationRecord
|
|||
transition [:queued, :started] => :failed
|
||||
end
|
||||
|
||||
state :queued, value: 0
|
||||
state :started, value: 1
|
||||
state :finished, value: 2
|
||||
state :failed, value: 3
|
||||
state :queued, value: STATUS[:queued]
|
||||
state :started, value: STATUS[:started]
|
||||
state :finished, value: STATUS[:finished]
|
||||
state :failed, value: STATUS[:failed]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ module Ci
|
|||
class CreatePipelineService < BaseService
|
||||
attr_reader :pipeline, :logger
|
||||
|
||||
CreateError = Class.new(StandardError)
|
||||
|
||||
LOG_MAX_DURATION_THRESHOLD = 3.seconds
|
||||
LOG_MAX_PIPELINE_SIZE = 2_000
|
||||
LOG_MAX_CREATION_THRESHOLD = 20.seconds
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
module ImportExport
|
||||
class ParallelExportService
|
||||
def initialize(export_job, current_user, after_export_strategy)
|
||||
@export_job = export_job
|
||||
@current_user = current_user
|
||||
@after_export_strategy = after_export_strategy
|
||||
@shared = project.import_export_shared
|
||||
@logger = Gitlab::Export::Logger.build
|
||||
end
|
||||
|
||||
def execute
|
||||
log_info('Parallel project export started')
|
||||
|
||||
if save_exporters && save_export_archive
|
||||
log_info('Parallel project export finished successfully')
|
||||
execute_after_export_action(after_export_strategy)
|
||||
else
|
||||
notify_error
|
||||
end
|
||||
|
||||
ensure
|
||||
cleanup
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :export_job, :current_user, :after_export_strategy, :shared, :logger
|
||||
|
||||
delegate :project, to: :export_job
|
||||
|
||||
def execute_after_export_action(after_export_strategy)
|
||||
return if after_export_strategy.execute(current_user, project)
|
||||
|
||||
notify_error
|
||||
end
|
||||
|
||||
def exporters
|
||||
[version_saver, exported_relations_merger]
|
||||
end
|
||||
|
||||
def save_exporters
|
||||
exporters.all? do |exporter|
|
||||
log_info("Parallel project export - #{exporter.class.name} saver started")
|
||||
|
||||
exporter.save
|
||||
end
|
||||
end
|
||||
|
||||
def save_export_archive
|
||||
Gitlab::ImportExport::Saver.save(exportable: project, shared: shared)
|
||||
end
|
||||
|
||||
def version_saver
|
||||
@version_saver ||= Gitlab::ImportExport::VersionSaver.new(shared: shared)
|
||||
end
|
||||
|
||||
def exported_relations_merger
|
||||
@relation_saver ||= Gitlab::ImportExport::Project::ExportedRelationsMerger.new(
|
||||
export_job: export_job,
|
||||
shared: shared)
|
||||
end
|
||||
|
||||
def cleanup
|
||||
FileUtils.rm_rf(shared.export_path) if File.exist?(shared.export_path)
|
||||
FileUtils.rm_rf(shared.archive_path) if File.exist?(shared.archive_path)
|
||||
end
|
||||
|
||||
def log_info(message)
|
||||
logger.info(
|
||||
message: message,
|
||||
**log_base_data
|
||||
)
|
||||
end
|
||||
|
||||
def notify_error
|
||||
logger.error(
|
||||
message: 'Parallel project export error',
|
||||
export_errors: shared.errors.join(', '),
|
||||
export_job_id: export_job.id,
|
||||
**log_base_data
|
||||
)
|
||||
|
||||
NotificationService.new.project_not_exported(project, current_user, shared.errors)
|
||||
end
|
||||
|
||||
def log_base_data
|
||||
{
|
||||
project_id: project.id,
|
||||
project_name: project.name,
|
||||
project_path: project.full_path
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
- ref = local_assigns.fetch(:ref, @ref)
|
||||
- form_path = local_assigns.fetch(:form_path, switch_project_refs_path(@project))
|
||||
- dropdown_toggle_text = @id || @project.default_branch
|
||||
- dropdown_toggle_text = ref || @project.default_branch
|
||||
- field_name = local_assigns.fetch(:field_name, 'ref')
|
||||
|
||||
= form_tag form_path, method: :get, class: "project-refs-form" do
|
||||
|
|
|
|||
|
|
@ -3009,6 +3009,15 @@
|
|||
:weight: 1
|
||||
:idempotent: false
|
||||
:tags: []
|
||||
- :name: projects_import_export_parallel_project_export
|
||||
:worker_name: Projects::ImportExport::ParallelProjectExportWorker
|
||||
:feature_category: :importers
|
||||
:has_external_dependencies: false
|
||||
:urgency: :low
|
||||
:resource_boundary: :memory
|
||||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: projects_import_export_relation_export
|
||||
:worker_name: Projects::ImportExport::RelationExportWorker
|
||||
:feature_category: :importers
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
module ImportExport
|
||||
class ParallelProjectExportWorker
|
||||
include ApplicationWorker
|
||||
include ExceptionBacktrace
|
||||
|
||||
idempotent!
|
||||
data_consistency :always
|
||||
deduplicate :until_executed
|
||||
feature_category :importers
|
||||
worker_resource_boundary :memory
|
||||
urgency :low
|
||||
loggable_arguments 1, 2
|
||||
sidekiq_options retries: 3, dead: false, status_expiration: StuckExportJobsWorker::EXPORT_JOBS_EXPIRATION
|
||||
|
||||
sidekiq_retries_exhausted do |job, exception|
|
||||
export_job = ProjectExportJob.find(job['args'].first)
|
||||
|
||||
export_job.fail_op!
|
||||
project = export_job.project
|
||||
|
||||
log_payload = {
|
||||
message: 'Parallel project export error',
|
||||
export_error: job['error_message'],
|
||||
project_export_job_id: export_job.id,
|
||||
project_name: project.name,
|
||||
project_id: project.id
|
||||
}
|
||||
Gitlab::ExceptionLogFormatter.format!(exception, log_payload)
|
||||
Gitlab::Export::Logger.error(log_payload)
|
||||
end
|
||||
|
||||
def perform(project_export_job_id, user_id, after_export_strategy = {})
|
||||
export_job = ProjectExportJob.find(project_export_job_id)
|
||||
|
||||
return if export_job.finished?
|
||||
|
||||
export_job.update_attribute(:jid, jid)
|
||||
current_user = User.find(user_id)
|
||||
after_export = build!(after_export_strategy)
|
||||
|
||||
export_service = ::Projects::ImportExport::ParallelExportService.new(export_job, current_user, after_export)
|
||||
export_service.execute
|
||||
|
||||
export_job.finish!
|
||||
rescue Gitlab::ImportExport::AfterExportStrategyBuilder::StrategyNotFoundError
|
||||
export_job.fail_op!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build!(after_export_strategy)
|
||||
strategy_klass = after_export_strategy&.delete('klass')
|
||||
|
||||
Gitlab::ImportExport::AfterExportStrategyBuilder.build!(strategy_klass, after_export_strategy)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -405,6 +405,8 @@
|
|||
- 1
|
||||
- - projects_git_garbage_collect
|
||||
- 1
|
||||
- - projects_import_export_parallel_project_export
|
||||
- 1
|
||||
- - projects_import_export_relation_export
|
||||
- 1
|
||||
- - projects_inactive_projects_deletion_notification
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ POST /bulk_imports
|
|||
| `entities[source_full_path]` | String | yes | Source full path of the entity to import. |
|
||||
| `entities[destination_name]` | String | yes | Deprecated: Use :destination_slug instead. Destination slug for the entity. |
|
||||
| `entities[destination_slug]` | String | yes | Destination slug for the entity. |
|
||||
| `entities[destination_namespace]` | String | no | Destination namespace for the entity. |
|
||||
| `entities[destination_namespace]` | String | yes | Destination namespace for the entity. |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/bulk_imports" \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,248 @@
|
|||
---
|
||||
stage: Package
|
||||
group: Package Registry
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Publish packages with Yarn
|
||||
|
||||
Publish npm packages in your project's Package Registry using Yarn. Then install the
|
||||
packages whenever you need to use them as a dependency.
|
||||
|
||||
Learn how to build a [yarn](../workflows/build_packages.md#yarn) package.
|
||||
|
||||
You can get started with Yarn 2 by following the [Yarn documentation](https://yarnpkg.com/getting-started/install/).
|
||||
|
||||
## Publish to GitLab Package Registry
|
||||
|
||||
### Authentication to the Package Registry
|
||||
|
||||
You need a token to publish a package. Different tokens are available depending on what you're trying to
|
||||
achieve. For more information, review the [guidance on tokens](../../../user/packages/package_registry/index.md#authenticate-with-the-registry).
|
||||
|
||||
- If your organization uses two-factor authentication (2FA), you must use a personal access token with the scope set to `api`.
|
||||
- If you publish a package via CI/CD pipelines, you must use a CI job token.
|
||||
|
||||
Create a token and save it to use later in the process.
|
||||
|
||||
### Naming convention
|
||||
|
||||
Depending on how you install the package, you may need to adhere to the naming convention.
|
||||
|
||||
You can use one of two API endpoints to install packages:
|
||||
|
||||
- **Instance-level**: Use when you have many npm packages in different GitLab groups or in their own namespace.
|
||||
- **Project-level**: Use when you have a few npm packages, and they are not in the same GitLab group.
|
||||
|
||||
If you plan to install a package through the [project level](#install-from-the-project-level), you do not have to
|
||||
adhere to the naming convention.
|
||||
|
||||
If you plan to install a package through the [instance level](#install-from-the-instance-level), then you must name
|
||||
your package with a [scope](https://docs.npmjs.com/misc/scope/). Scoped packages begin with a `@` and have the
|
||||
`@owner/package-name` format. You can set up the scope for your package in the `.yarnrc.yml` file and by using the
|
||||
`publishConfig` option in the `package.json`.
|
||||
|
||||
- The value used for the `@scope` is the root of the project that hosts the packages and not the root
|
||||
of the project with the package's source code. The scope should be lowercase.
|
||||
- The package name can be anything you want
|
||||
|
||||
| Project URL | Package Registry in | Scope | Full package name |
|
||||
| ------------------------------------------------------- | ------------------- | --------- | ---------------------- |
|
||||
| `https://gitlab.com/my-org/engineering-group/analytics` | Analytics | `@my-org` | `@my-org/package-name` |
|
||||
|
||||
### Configuring `.yarnrc.yml` to publish from the project level
|
||||
|
||||
To publish with the project-level npm endpoint, set the following configuration in
|
||||
`.yarnrc.yml`:
|
||||
|
||||
```yaml
|
||||
npmScopes:
|
||||
foo:
|
||||
npmRegistryServer: 'https://<your_domain>/api/v4/projects/<your_project_id>/packages/npm/'
|
||||
npmPublishRegistry: 'https://<your_domain>/api/v4/projects/<your_project_id>/packages/npm/'
|
||||
|
||||
npmRegistries:
|
||||
//gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: '<your_token>'
|
||||
```
|
||||
|
||||
In this configuration:
|
||||
|
||||
- Replace `<your_domain>` with your domain name.
|
||||
- Replace `<your_project_id>` with your project's ID, which you can find on the project's home page.
|
||||
- Replace `<your_token>` with a deploy token, group access token, project access token, or personal access token.
|
||||
|
||||
### Configuring `.yarnrc.yml` to publish from the instance level
|
||||
|
||||
For the instance-level npm endpoint, use this Yarn 2 configuration in `.yarnrc.yml`:
|
||||
|
||||
```yaml
|
||||
npmScopes:
|
||||
<scope>:
|
||||
npmRegistryServer: 'https://<your_domain>/api/v4/packages/npm/'
|
||||
|
||||
npmRegistries:
|
||||
//gitlab.example.com/api/v4/packages/npm/:
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: '<your_token>'
|
||||
```
|
||||
|
||||
In this configuration:
|
||||
|
||||
- Replace `<your_domain>` with your domain name.
|
||||
- Your scope is `<scope>`, without `@`.
|
||||
- Replace `<your_token>` with a deploy token, group access token, project access token, or personal access token.
|
||||
|
||||
### Publishing a package via the command line
|
||||
|
||||
Publish a package:
|
||||
|
||||
```shell
|
||||
npm publish
|
||||
```
|
||||
|
||||
Your package should now publish to the Package Registry.
|
||||
|
||||
### Publishing via a CI/CD pipeline
|
||||
|
||||
In the GitLab project that houses your `yarnrc.yml`, edit or create a `.gitlab-ci.yml` file. For example:
|
||||
|
||||
```yaml
|
||||
image: node:latest
|
||||
|
||||
stages:
|
||||
- deploy
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script:
|
||||
- npm publish
|
||||
```
|
||||
|
||||
Your package should now publish to the Package Registry when the pipeline runs.
|
||||
|
||||
## Install a package
|
||||
|
||||
If multiple packages have the same name and version, the most recently-published package is retrieved when you install a package.
|
||||
|
||||
You can install a package from a GitLab project or instance:
|
||||
|
||||
- **Instance-level**: Use when you have many npm packages in different GitLab groups or in their own namespace.
|
||||
- **Project-level**: Use when you have a few npm packages, and they are not in the same GitLab group.
|
||||
|
||||
### Install from the instance level
|
||||
|
||||
WARNING:
|
||||
You must use packages published with the scoped [naming convention](#naming-convention) when you install a package from the instance level.
|
||||
|
||||
1. Authenticate to the Package Registry
|
||||
|
||||
If you install a package from a private project, you must authenticate to the Package Registry. Skip this step if the project is not private.
|
||||
|
||||
```shell
|
||||
npm config set -- //your_domain_name/api/v4/packages/npm/:_authToken=your_token
|
||||
```
|
||||
|
||||
- Replace `your_domain_name` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `your_token` with a deploy token, group access token, project access token, or personal access token.
|
||||
|
||||
1. Set the registry
|
||||
|
||||
```shell
|
||||
npm config set @scope:registry https://your_domain_name.com/api/v4/packages/npm/
|
||||
```
|
||||
|
||||
- Replace `@scope` with the [root level group](#naming-convention) of the project you're installing to the package from.
|
||||
- Replace `your_domain_name` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `your_token` with a deploy token, group access token, project access token, or personal access token.
|
||||
|
||||
1. Install the package
|
||||
|
||||
```shell
|
||||
yarn add @scope/my-package
|
||||
```
|
||||
|
||||
### Install from the project level
|
||||
|
||||
1. Authenticate to the Package Registry
|
||||
|
||||
If you install a package from a private project, you must authenticate to the Package Registry. Skip this step if the project is not private.
|
||||
|
||||
```shell
|
||||
npm config set -- //your_domain_name/api/v4/projects/your_project_id/packages/npm/:_authToken=your_token
|
||||
```
|
||||
|
||||
- Replace `your_domain_name` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `your_project_id` is your project ID, found on the project's home page.
|
||||
- Replace `your_token` with a deploy token, group access token, project access token, or personal access token.
|
||||
|
||||
1. Set the registry
|
||||
|
||||
```shell
|
||||
npm config set @scope:registry=https://your_domain_name/api/v4/projects/your_project_id/packages/npm/
|
||||
```
|
||||
|
||||
- Replace `@scope` with the [root level group](#naming-convention) of the project you're installing to the package from.
|
||||
- Replace `your_domain_name` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `your_project_id` is your project ID, found on the project's home page.
|
||||
|
||||
1. Install the package
|
||||
|
||||
```shell
|
||||
yarn add @scope/my-package
|
||||
```
|
||||
|
||||
## Helpful hints
|
||||
|
||||
For full helpful hints information, refer to the [npm documentation](../npm_registry/index.md#helpful-hints).
|
||||
|
||||
### Supported CLI commands
|
||||
|
||||
The GitLab npm repository supports the following commands for the npm CLI (`npm`) and yarn CLI
|
||||
(`yarn`):
|
||||
|
||||
- `npm install`: Install npm packages.
|
||||
- `npm publish`: Publish an npm package to the registry.
|
||||
- `npm dist-tag add`: Add a dist-tag to an npm package.
|
||||
- `npm dist-tag ls`: List dist-tags for a package.
|
||||
- `npm dist-tag rm`: Delete a dist-tag.
|
||||
- `npm ci`: Install npm packages directly from your `package-lock.json` file.
|
||||
- `npm view`: Show package metadata.
|
||||
- `yarn add`: Install an npm package.
|
||||
- `yarn update`: Update your dependencies.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For full troubleshooting information, refer to the [npm documentation](../npm_registry/index.md#troubleshooting).
|
||||
|
||||
### Error running Yarn with the Package Registry for the npm registry
|
||||
|
||||
If you are using [Yarn](https://classic.yarnpkg.com/en/) with the npm registry, you may get
|
||||
an error message like:
|
||||
|
||||
```shell
|
||||
yarn install v1.15.2
|
||||
warning package.json: No license field
|
||||
info No lockfile found.
|
||||
warning XXX: No license field
|
||||
[1/4] 🔍 Resolving packages...
|
||||
[2/4] 🚚 Fetching packages...
|
||||
error An unexpected error occurred: "https://gitlab.example.com/api/v4/projects/XXX/packages/npm/XXX/XXX/-/XXX/XXX-X.X.X.tgz: Request failed \"404 Not Found\"".
|
||||
info If you think this is a bug, please open a bug report with the information provided in "/Users/XXX/gitlab-migration/module-util/yarn-error.log".
|
||||
info Visit https://classic.yarnpkg.com/en/docs/cli/install for documentation about this command
|
||||
```
|
||||
|
||||
In this case, try adding this to your `.npmrc` file (and replace `<your_token>`
|
||||
with your personal access token or deploy token):
|
||||
|
||||
```plaintext
|
||||
//gitlab.example.com/api/v4/projects/:_authToken=<your_token>
|
||||
```
|
||||
|
||||
You can also use `yarn config` instead of `npm config` when setting your auth-token dynamically:
|
||||
|
||||
```shell
|
||||
yarn config set '//gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken' "<your_token>"
|
||||
yarn config set '//gitlab.example.com/api/v4/packages/npm/:_authToken' "<your_token>"
|
||||
```
|
||||
|
|
@ -76,7 +76,7 @@ For each project and group you can select one of the following levels:
|
|||
| Participate | Receive notifications for threads you have participated in. |
|
||||
| On mention | Receive notifications when you are [mentioned](../discussions/index.md#mentions) in a comment. |
|
||||
| Disabled | Receive no notifications. |
|
||||
| Custom | Receive notifications for selected events. |
|
||||
| Custom | Receive notifications for selected events and threads you have participated in. |
|
||||
|
||||
### Global notification settings
|
||||
|
||||
|
|
|
|||
|
|
@ -214,19 +214,3 @@ Prerequisites:
|
|||
- A deploy token with `read_registry` and `write_registry` scopes.
|
||||
|
||||
Follow the dependency proxy [authentication instructions](../../packages/dependency_proxy/index.md).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error: `api error: Repository or object not found:`
|
||||
|
||||
When using a group deploy token to clone from LFS objects, you might get `404 Not Found` responses
|
||||
and this error message. This occurs because of a bug, documented in
|
||||
[issue 235398](https://gitlab.com/gitlab-org/gitlab/-/issues/235398).
|
||||
|
||||
```plaintext
|
||||
api error: Repository or object not found:
|
||||
https://<URL-with-token>.git/info/lfs/objects/batch
|
||||
Check that it exists and that you have proper access to it
|
||||
```
|
||||
|
||||
The workaround is to use a project deploy token.
|
||||
|
|
|
|||
|
|
@ -22032,6 +22032,9 @@ msgstr ""
|
|||
msgid "Integrations|Enable comments"
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Enable slash commands and notifications for a Slack workspace."
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Ensure your instance URL is correct and your instance is configured correctly. %{linkStart}Learn more%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -245,9 +245,9 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
|
|||
it 'expands multiple queue groups correctly' do
|
||||
expected_workers =
|
||||
if Gitlab.ee?
|
||||
[%w[chat_notification], %w[project_export projects_import_export_relation_export project_template_export]]
|
||||
[%w[chat_notification], %w[project_export projects_import_export_parallel_project_export projects_import_export_relation_export project_template_export]]
|
||||
else
|
||||
[%w[chat_notification], %w[project_export projects_import_export_relation_export]]
|
||||
[%w[chat_notification], %w[project_export projects_import_export_parallel_project_export projects_import_export_relation_export]]
|
||||
end
|
||||
|
||||
expect(Gitlab::SidekiqCluster)
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ RSpec.describe Explore::ProjectsController do
|
|||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when topic exists' do
|
||||
before do
|
||||
create(:topic, name: 'topic1')
|
||||
|
|
|
|||
|
|
@ -757,6 +757,7 @@ RSpec.describe Projects::NotesController do
|
|||
expect { put :update, params: request_params }.to change { note.reload.note }
|
||||
end
|
||||
end
|
||||
|
||||
context "doesnt update the note" do
|
||||
let(:issue) { create(:issue, :confidential, project: project) }
|
||||
let(:note) { create(:note, noteable: issue, project: project) }
|
||||
|
|
|
|||
|
|
@ -4,5 +4,21 @@ FactoryBot.define do
|
|||
factory :project_export_job do
|
||||
project
|
||||
jid { SecureRandom.hex(8) }
|
||||
|
||||
trait :queued do
|
||||
status { ProjectExportJob::STATUS[:queued] }
|
||||
end
|
||||
|
||||
trait :started do
|
||||
status { ProjectExportJob::STATUS[:started] }
|
||||
end
|
||||
|
||||
trait :finished do
|
||||
status { ProjectExportJob::STATUS[:finished] }
|
||||
end
|
||||
|
||||
trait :failed do
|
||||
status { ProjectExportJob::STATUS[:failed] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ FactoryBot.define do
|
|||
factory :project_ci_feature_usage, class: 'Projects::CiFeatureUsage' do
|
||||
project factory: :project
|
||||
feature { :code_coverage } # rubocop: disable RSpec/EmptyExampleGroup
|
||||
|
||||
default_branch { false }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,9 +27,11 @@ RSpec.describe 'Internal Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
|
|
@ -47,9 +49,11 @@ RSpec.describe 'Internal Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
|
|
@ -69,9 +73,11 @@ RSpec.describe 'Internal Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
|
|
@ -89,9 +95,11 @@ RSpec.describe 'Internal Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
|
|
@ -109,9 +117,11 @@ RSpec.describe 'Internal Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_denied_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_denied_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_denied_for(:developer).of(group) }
|
||||
|
|
|
|||
|
|
@ -27,9 +27,11 @@ RSpec.describe 'Private Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_denied_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
|
|
@ -47,9 +49,11 @@ RSpec.describe 'Private Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_denied_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
|
|
@ -69,9 +73,11 @@ RSpec.describe 'Private Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_denied_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
|
|
@ -89,9 +95,11 @@ RSpec.describe 'Private Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_denied_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
|
|
@ -109,9 +117,11 @@ RSpec.describe 'Private Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_denied_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_denied_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_denied_for(:developer).of(group) }
|
||||
|
|
@ -135,9 +145,11 @@ RSpec.describe 'Private Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_denied_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
|
|
|
|||
|
|
@ -27,9 +27,11 @@ RSpec.describe 'Public Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
|
|
@ -47,9 +49,11 @@ RSpec.describe 'Public Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
|
|
@ -69,9 +73,11 @@ RSpec.describe 'Public Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
|
|
@ -89,9 +95,11 @@ RSpec.describe 'Public Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
|
|
@ -109,9 +117,11 @@ RSpec.describe 'Public Group access', feature_category: :permissions do
|
|||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.to be_denied_for(:admin) }
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_denied_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_denied_for(:developer).of(group) }
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ RSpec.describe BlobHelper do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'viewer related' do
|
||||
include FakeBlobHelpers
|
||||
|
||||
|
|
|
|||
|
|
@ -15,11 +15,13 @@ RSpec.describe GitHelper do
|
|||
|
||||
it { expect(strip_signature).to eq("Version 1.69.0\n\n") }
|
||||
end
|
||||
|
||||
context 'strips PGP MESSAGE' do
|
||||
let(:strip_signature) { helper.strip_signature( pgp_message_tag ) }
|
||||
|
||||
it { expect(strip_signature).to eq("Version 1.69.0\n\n") }
|
||||
end
|
||||
|
||||
context 'strips SIGNED MESSAGE' do
|
||||
let(:strip_signature) { helper.strip_signature( x509_message_tag ) }
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ RSpec.describe 'lograge', type: :request do
|
|||
skip_memory_instrumentation!
|
||||
end
|
||||
|
||||
it 'logs memory usage metrics' do
|
||||
it 'logs memory usage metrics', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/384081' do
|
||||
expect(Lograge.formatter).to receive(:call)
|
||||
.with(a_hash_including(:mem_objects))
|
||||
.and_call_original
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ RSpec.describe BulkImports::Projects::Pipelines::IssuesPipeline do
|
|||
expect(award_emoji.user).to eq(user)
|
||||
end
|
||||
end
|
||||
|
||||
context 'issue state' do
|
||||
let(:issue_attributes) { { 'state' => 'closed' } }
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ RSpec.describe Gitlab::BlobHelper do
|
|||
expect(blob.image?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a .webp file' do
|
||||
it 'returns true' do
|
||||
expect(webp_blob.image?).to be_truthy
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ RSpec.describe Gitlab::FileTypeDetection do
|
|||
expect(described_class.extension_match?('my/file.foo', extensions)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when class is an uploader' do
|
||||
let(:uploader) do
|
||||
example_uploader = Class.new(CarrierWave::Uploader::Base) do
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ RSpec.describe Gitlab::InstrumentationHelper do
|
|||
skip_memory_instrumentation!
|
||||
end
|
||||
|
||||
it 'logs memory usage metrics' do
|
||||
it 'logs memory usage metrics', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/384081' do
|
||||
subject
|
||||
|
||||
expect(payload).to include(
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ RSpec.describe Gitlab::Memory::Instrumentation do
|
|||
end
|
||||
|
||||
describe '.available?' do
|
||||
it 'returns true' do
|
||||
it 'returns true', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/384081' do
|
||||
expect(described_class).to be_available
|
||||
end
|
||||
end
|
||||
|
|
@ -18,7 +18,7 @@ RSpec.describe Gitlab::Memory::Instrumentation do
|
|||
describe '.start_thread_memory_allocations' do
|
||||
subject { described_class.start_thread_memory_allocations }
|
||||
|
||||
it 'a hash is returned' do
|
||||
it 'a hash is returned', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/384081' do
|
||||
is_expected.to be_a(Hash)
|
||||
end
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ RSpec.describe Gitlab::Memory::Instrumentation do
|
|||
expect(described_class).to receive(:measure_thread_memory_allocations).and_call_original
|
||||
end
|
||||
|
||||
it 'a hash is returned' do
|
||||
it 'a hash is returned', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/384081' do
|
||||
result = subject
|
||||
expect(result).to include(
|
||||
mem_objects: be > 1000,
|
||||
|
|
|
|||
|
|
@ -452,6 +452,7 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
|
|||
expect(subject).to eq("current_rss(#{rss}) > soft_limit_rss(#{soft_limit}) longer than GRACE_BALLOON_SECONDS(#{grace_balloon_seconds})")
|
||||
end
|
||||
end
|
||||
|
||||
context 'deadline not exceeded' do
|
||||
let(:deadline_exceeded) { false }
|
||||
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ RSpec.describe TokenAuthenticatableStrategies::Encrypted do
|
|||
expect(subject.set_token(instance, 'my-value')).to eq 'my-value'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when encryption is optional' do
|
||||
let(:options) { { encrypted: :optional } }
|
||||
|
||||
|
|
|
|||
|
|
@ -579,6 +579,7 @@ RSpec.describe Note do
|
|||
expect(commit_note.confidential?).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when note is confidential' do
|
||||
it 'is true even when a noteable is not confidential' do
|
||||
issue = create(:issue, confidential: false)
|
||||
|
|
|
|||
|
|
@ -291,6 +291,7 @@ RSpec.describe ProjectFeature do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Gitlab/FeatureAvailableUsage
|
||||
describe '#feature_available?' do
|
||||
let(:features) { ProjectFeature::FEATURES }
|
||||
|
|
|
|||
|
|
@ -3217,6 +3217,7 @@ RSpec.describe User do
|
|||
expect(described_class.find_by_full_path('unknown')).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the follow_redirects option set to true' do
|
||||
it 'returns nil' do
|
||||
expect(described_class.find_by_full_path('unknown', follow_redirects: true)).to eq(nil)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ RSpec.describe ZoomMeeting do
|
|||
expect(meetings_added).not_to include(removed_meeting.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.removed_from_issue' do
|
||||
it 'gets only removed meetings' do
|
||||
meetings_removed = described_class.removed_from_issue.pluck(:id)
|
||||
|
|
|
|||
|
|
@ -4687,6 +4687,7 @@ RSpec.describe API::Projects do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /projects/:id/transfer' do
|
||||
context 'when authenticated as owner' do
|
||||
let(:group) { create :group }
|
||||
|
|
|
|||
|
|
@ -301,6 +301,7 @@ RSpec.describe 'project routing' do
|
|||
expect(get('/gitlab/gitlabhq/-/merge_requests/1/conflicts')).to route_to('projects/merge_requests/conflicts#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
|
||||
end
|
||||
end
|
||||
|
||||
# raw_project_snippet GET /:project_id/snippets/:id/raw(.:format) snippets#raw
|
||||
# project_snippets GET /:project_id/snippets(.:format) snippets#index
|
||||
# new_project_snippet GET /:project_id/snippets/new(.:format) snippets#new
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::ImportExport::ParallelExportService, feature_category: :importers do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:export_job) { create(:project_export_job) }
|
||||
let(:after_export_strategy) { Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy.new }
|
||||
let(:project) { export_job.project }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(Gitlab::ImportExport::Project::ExportedRelationsMerger) do |saver|
|
||||
allow(saver).to receive(:save).and_return(true)
|
||||
end
|
||||
|
||||
allow_next_instance_of(Gitlab::ImportExport::VersionSaver) do |saver|
|
||||
allow(saver).to receive(:save).and_return(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
subject(:service) { described_class.new(export_job, user, after_export_strategy) }
|
||||
|
||||
it 'creates a project export archive file' do
|
||||
expect(Gitlab::ImportExport::Saver).to receive(:save)
|
||||
.with(exportable: project, shared: project.import_export_shared)
|
||||
|
||||
service.execute
|
||||
end
|
||||
|
||||
it 'logs export progress' do
|
||||
allow(Gitlab::ImportExport::Saver).to receive(:save).and_return(true)
|
||||
|
||||
logger = service.instance_variable_get(:@logger)
|
||||
messages = [
|
||||
'Parallel project export started',
|
||||
'Parallel project export - Gitlab::ImportExport::VersionSaver saver started',
|
||||
'Parallel project export - Gitlab::ImportExport::Project::ExportedRelationsMerger saver started',
|
||||
'Parallel project export finished successfully'
|
||||
]
|
||||
messages.each do |message|
|
||||
expect(logger).to receive(:info).ordered.with(hash_including(message: message))
|
||||
end
|
||||
|
||||
service.execute
|
||||
end
|
||||
|
||||
it 'executes after export stragegy on export success' do
|
||||
allow(Gitlab::ImportExport::Saver).to receive(:save).and_return(true)
|
||||
|
||||
expect(after_export_strategy).to receive(:execute)
|
||||
|
||||
service.execute
|
||||
end
|
||||
|
||||
it 'ensures files are cleaned up' do
|
||||
shared = project.import_export_shared
|
||||
FileUtils.mkdir_p(shared.archive_path)
|
||||
FileUtils.mkdir_p(shared.export_path)
|
||||
|
||||
allow(Gitlab::ImportExport::Saver).to receive(:save).and_raise(StandardError)
|
||||
|
||||
expect { service.execute }.to raise_error(StandardError)
|
||||
|
||||
expect(File.exist?(shared.export_path)).to eq(false)
|
||||
expect(File.exist?(shared.archive_path)).to eq(false)
|
||||
end
|
||||
|
||||
context 'when export fails' do
|
||||
it 'notifies the error to the user' do
|
||||
allow(Gitlab::ImportExport::Saver).to receive(:save).and_return(false)
|
||||
|
||||
allow(project.import_export_shared).to receive(:errors).and_return(['Error'])
|
||||
|
||||
expect_next_instance_of(NotificationService) do |instance|
|
||||
expect(instance).to receive(:project_not_exported).with(project, user, ['Error'])
|
||||
end
|
||||
|
||||
service.execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'when after export stragegy fails' do
|
||||
it 'notifies the error to the user' do
|
||||
allow(Gitlab::ImportExport::Saver).to receive(:save).and_return(true)
|
||||
allow(after_export_strategy).to receive(:execute).and_return(false)
|
||||
allow(project.import_export_shared).to receive(:errors).and_return(['Error'])
|
||||
|
||||
expect_next_instance_of(NotificationService) do |instance|
|
||||
expect(instance).to receive(:project_not_exported).with(project, user, ['Error'])
|
||||
end
|
||||
|
||||
service.execute
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::ImportExport::ParallelProjectExportWorker, feature_category: :importers do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:export_job) { create(:project_export_job, :started) }
|
||||
let(:after_export_strategy) { {} }
|
||||
let(:job_args) { [export_job.id, user.id, after_export_strategy] }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(described_class) do |job|
|
||||
allow(job).to receive(:jid) { SecureRandom.hex(8) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
it_behaves_like 'an idempotent worker' do
|
||||
it 'sets the export job status to finished' do
|
||||
subject
|
||||
|
||||
expect(export_job.reload.finished?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when after export strategy does not exist' do
|
||||
let(:after_export_strategy) { { 'klass' => 'InvalidStrategy' } }
|
||||
|
||||
it 'sets the export job status to failed' do
|
||||
described_class.new.perform(*job_args)
|
||||
|
||||
expect(export_job.reload.failed?).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.sidekiq_retries_exhausted' do
|
||||
let(:job) { { 'args' => job_args, 'error_message' => 'Error message' } }
|
||||
|
||||
it 'sets export_job status to failed' do
|
||||
described_class.sidekiq_retries_exhausted_block.call(job)
|
||||
|
||||
expect(export_job.reload.failed?).to eq(true)
|
||||
end
|
||||
|
||||
it 'logs an error message' do
|
||||
expect_next_instance_of(Gitlab::Export::Logger) do |logger|
|
||||
expect(logger).to receive(:error).with(
|
||||
hash_including(
|
||||
message: 'Parallel project export error',
|
||||
export_error: 'Error message'
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
described_class.sidekiq_retries_exhausted_block.call(job)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue