Merge branch '41899-api-endpoint-for-importing-a-project-export' into 'master'
Resolve "API endpoint for importing a project export" Closes #41899 See merge request gitlab-org/gitlab-ce!17025
This commit is contained in:
commit
feab47e082
9 changed files with 260 additions and 0 deletions
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: API endpoint for importing a project export
|
||||
merge_request: 17025
|
||||
author:
|
||||
type: added
|
|
@ -43,6 +43,7 @@ following locations:
|
|||
- [Pipeline Schedules](pipeline_schedules.md)
|
||||
- [Projects](projects.md) including setting Webhooks
|
||||
- [Project Access Requests](access_requests.md)
|
||||
- [Project import/export](project_import_export.md)
|
||||
- [Project Members](members.md)
|
||||
- [Project Snippets](project_snippets.md)
|
||||
- [Protected Branches](protected_branches.md)
|
||||
|
|
74
doc/api/project_import_export.md
Normal file
74
doc/api/project_import_export.md
Normal file
|
@ -0,0 +1,74 @@
|
|||
# Project import API
|
||||
|
||||
[Introduced][ce-41899] in GitLab 10.6
|
||||
|
||||
[See also the project import/export documentation](../user/project/settings/import_export.md)
|
||||
|
||||
## Import a file
|
||||
|
||||
```http
|
||||
POST /projects/import
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | -------------- | -------- | ---------------------------------------- |
|
||||
| `namespace` | integer/string | no | The ID or path of the namespace that the project will be imported to. Defaults to the current user's namespace |
|
||||
| `file` | string | yes | The file to be uploaded |
|
||||
| `path` | string | yes | Name and path for new project |
|
||||
|
||||
To upload a file from your filesystem, use the `--form` argument. This causes
|
||||
cURL to post data using the header `Content-Type: multipart/form-data`.
|
||||
The `file=` parameter must point to a file on your filesystem and be preceded
|
||||
by `@`. For example:
|
||||
|
||||
```console
|
||||
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "path=api-project" --form "file=@/path/to/file" https://gitlab.example.com/api/v4/projects/import
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"description": null,
|
||||
"name": "api-project",
|
||||
"name_with_namespace": "Administrator / api-project",
|
||||
"path": "api-project",
|
||||
"path_with_namespace": "root/api-project",
|
||||
"created_at": "2018-02-13T09:05:58.023Z",
|
||||
"import_status": "scheduled"
|
||||
}
|
||||
```
|
||||
|
||||
## Import status
|
||||
|
||||
Get the status of an import.
|
||||
|
||||
```http
|
||||
GET /projects/:id/import
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | -------------- | -------- | ---------------------------------------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
|
||||
```console
|
||||
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/import
|
||||
```
|
||||
|
||||
Status can be one of `none`, `scheduled`, `failed`, `started`, or `finished`.
|
||||
|
||||
If the status is `failed`, it will include the import error message under `import_error`.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"description": "Itaque perspiciatis minima aspernatur corporis consequatur.",
|
||||
"name": "Gitlab Test",
|
||||
"name_with_namespace": "Gitlab Org / Gitlab Test",
|
||||
"path": "gitlab-test",
|
||||
"path_with_namespace": "gitlab-org/gitlab-test",
|
||||
"created_at": "2017-08-29T04:36:44.383Z",
|
||||
"import_status": "started"
|
||||
}
|
||||
```
|
||||
|
||||
[ce-41899]: https://gitlab.com/gitlab-org/gitlab-ce/issues/41899
|
|
@ -1330,6 +1330,10 @@ POST /projects/:id/housekeeping
|
|||
|
||||
Read more in the [Branches](branches.md) documentation.
|
||||
|
||||
## Project Import/Export
|
||||
|
||||
Read more in the [Project import/export](project_import_export.md) documentation.
|
||||
|
||||
## Project members
|
||||
|
||||
Read more in the [Project members](members.md) documentation.
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
> in the import side is required to map the users, based on email or username.
|
||||
> Otherwise, a supplementary comment is left to mention the original author and
|
||||
> the MRs, notes or issues will be owned by the importer.
|
||||
> - Control project Import/Export with the [API](../../../api/project_import_export.md).
|
||||
|
||||
Existing projects running on any GitLab instance or GitLab.com can be exported
|
||||
with all their related data and be moved into a new GitLab instance.
|
||||
|
|
|
@ -138,6 +138,7 @@ module API
|
|||
mount ::API::PagesDomains
|
||||
mount ::API::Pipelines
|
||||
mount ::API::PipelineSchedules
|
||||
mount ::API::ProjectImport
|
||||
mount ::API::ProjectHooks
|
||||
mount ::API::Projects
|
||||
mount ::API::ProjectMilestones
|
||||
|
|
|
@ -91,6 +91,13 @@ module API
|
|||
expose :created_at
|
||||
end
|
||||
|
||||
class ProjectImportStatus < ProjectIdentity
|
||||
expose :import_status
|
||||
|
||||
# TODO: Use `expose_nil` once we upgrade the grape-entity gem
|
||||
expose :import_error, if: lambda { |status, _ops| status.import_error }
|
||||
end
|
||||
|
||||
class BasicProjectDetails < ProjectIdentity
|
||||
include ::API::ProjectsRelationBuilder
|
||||
|
||||
|
|
65
lib/api/project_import.rb
Normal file
65
lib/api/project_import.rb
Normal file
|
@ -0,0 +1,65 @@
|
|||
module API
|
||||
class ProjectImport < Grape::API
|
||||
include PaginationParams
|
||||
|
||||
helpers do
|
||||
def import_params
|
||||
declared_params(include_missing: false)
|
||||
end
|
||||
|
||||
def file_is_valid?
|
||||
import_params[:file] && import_params[:file]['tempfile'].respond_to?(:read)
|
||||
end
|
||||
|
||||
def validate_file!
|
||||
render_api_error!('The file is invalid', 400) unless file_is_valid?
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
forbidden! unless Gitlab::CurrentSettings.import_sources.include?('gitlab_project')
|
||||
end
|
||||
|
||||
resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
|
||||
params do
|
||||
requires :path, type: String, desc: 'The new project path and name'
|
||||
requires :file, type: File, desc: 'The project export file to be imported'
|
||||
optional :namespace, type: String, desc: "The ID or name of the namespace that the project will be imported into. Defaults to the current user's namespace."
|
||||
end
|
||||
desc 'Create a new project import' do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success Entities::ProjectImportStatus
|
||||
end
|
||||
post 'import' do
|
||||
validate_file!
|
||||
|
||||
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42437')
|
||||
|
||||
namespace = if import_params[:namespace]
|
||||
find_namespace!(import_params[:namespace])
|
||||
else
|
||||
current_user.namespace
|
||||
end
|
||||
|
||||
project_params = import_params.merge(namespace_id: namespace.id,
|
||||
file: import_params[:file]['tempfile'])
|
||||
project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute
|
||||
|
||||
render_api_error!(project.errors.full_messages&.first, 400) unless project.saved?
|
||||
|
||||
present project, with: Entities::ProjectImportStatus
|
||||
end
|
||||
|
||||
params do
|
||||
requires :id, type: String, desc: 'The ID of a project'
|
||||
end
|
||||
desc 'Get a project export status' do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success Entities::ProjectImportStatus
|
||||
end
|
||||
get ':id/import' do
|
||||
present user_project, with: Entities::ProjectImportStatus
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
102
spec/requests/api/project_import_spec.rb
Normal file
102
spec/requests/api/project_import_spec.rb
Normal file
|
@ -0,0 +1,102 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe API::ProjectImport do
|
||||
let(:export_path) { "#{Dir.tmpdir}/project_export_spec" }
|
||||
let(:user) { create(:user) }
|
||||
let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
|
||||
let(:namespace) { create(:group) }
|
||||
before do
|
||||
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
|
||||
|
||||
namespace.add_owner(user)
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.rm_rf(export_path, secure: true)
|
||||
end
|
||||
|
||||
describe 'POST /projects/import' do
|
||||
it 'schedules an import using a namespace' do
|
||||
stub_import(namespace)
|
||||
|
||||
post api('/projects/import', user), path: 'test-import', file: fixture_file_upload(file), namespace: namespace.id
|
||||
|
||||
expect(response).to have_gitlab_http_status(201)
|
||||
end
|
||||
|
||||
it 'schedules an import using the namespace path' do
|
||||
stub_import(namespace)
|
||||
|
||||
post api('/projects/import', user), path: 'test-import', file: fixture_file_upload(file), namespace: namespace.full_path
|
||||
|
||||
expect(response).to have_gitlab_http_status(201)
|
||||
end
|
||||
|
||||
it 'schedules an import at the user namespace level' do
|
||||
stub_import(user.namespace)
|
||||
|
||||
post api('/projects/import', user), path: 'test-import2', file: fixture_file_upload(file)
|
||||
|
||||
expect(response).to have_gitlab_http_status(201)
|
||||
end
|
||||
|
||||
it 'schedules an import at the user namespace level' do
|
||||
expect_any_instance_of(Project).not_to receive(:import_schedule)
|
||||
expect(Gitlab::ImportExport::ProjectCreator).not_to receive(:new)
|
||||
|
||||
post api('/projects/import', user), namespace: 'nonexistent', path: 'test-import2', file: fixture_file_upload(file)
|
||||
|
||||
expect(response).to have_gitlab_http_status(404)
|
||||
expect(json_response['message']).to eq('404 Namespace Not Found')
|
||||
end
|
||||
|
||||
it 'does not schedule an import if the user has no permission to the namespace' do
|
||||
expect_any_instance_of(Project).not_to receive(:import_schedule)
|
||||
|
||||
post(api('/projects/import', create(:user)),
|
||||
path: 'test-import3',
|
||||
file: fixture_file_upload(file),
|
||||
namespace: namespace.full_path)
|
||||
|
||||
expect(response).to have_gitlab_http_status(404)
|
||||
expect(json_response['message']).to eq('404 Namespace Not Found')
|
||||
end
|
||||
|
||||
it 'does not schedule an import if the user uploads no valid file' do
|
||||
expect_any_instance_of(Project).not_to receive(:import_schedule)
|
||||
|
||||
post api('/projects/import', user), path: 'test-import3', file: './random/test'
|
||||
|
||||
expect(response).to have_gitlab_http_status(400)
|
||||
expect(json_response['error']).to eq('file is invalid')
|
||||
end
|
||||
|
||||
def stub_import(namespace)
|
||||
expect_any_instance_of(Project).to receive(:import_schedule)
|
||||
expect(Gitlab::ImportExport::ProjectCreator).to receive(:new).with(namespace.id, any_args).and_call_original
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /projects/:id/import' do
|
||||
it 'returns the import status' do
|
||||
project = create(:project, import_status: 'started')
|
||||
project.add_master(user)
|
||||
|
||||
get api("/projects/#{project.id}/import", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(json_response).to include('import_status' => 'started')
|
||||
end
|
||||
|
||||
it 'returns the import status and the error if failed' do
|
||||
project = create(:project, import_status: 'failed', import_error: 'error')
|
||||
project.add_master(user)
|
||||
|
||||
get api("/projects/#{project.id}/import", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(json_response).to include('import_status' => 'failed',
|
||||
'import_error' => 'error')
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue