API for importing external repos

This commit is contained in:
Ben 2019-01-17 10:37:08 +00:00 committed by Dmitriy Zaporozhets
parent f598daf284
commit 1e2bd85333
8 changed files with 229 additions and 19 deletions

View File

@ -18,6 +18,7 @@ class Import::BaseController < ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
# deprecated: being replaced by app/services/import/base_service.rb
def find_or_create_namespace(names, owner)
names = params[:target_namespace].presence || names
@ -32,6 +33,7 @@ class Import::BaseController < ApplicationController
current_user.namespace
end
# deprecated: being replaced by app/services/import/base_service.rb
def project_save_error(project)
project.errors.full_messages.join(', ')
end

View File

@ -39,28 +39,21 @@ class Import::GithubController < Import::BaseController
end
def create
repo = client.repo(params[:repo_id].to_i)
project_name = params[:new_name].presence || repo.name
namespace_path = params[:target_namespace].presence || current_user.namespace_path
target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path)
result = Import::GithubService.new(client, current_user, import_params).execute(access_params, provider)
if can?(current_user, :create_projects, target_namespace)
project = Gitlab::LegacyGithubImport::ProjectCreator
.new(repo, project_name, target_namespace, current_user, access_params, type: provider)
.execute(extra_project_attrs)
if project.persisted?
render json: ProjectSerializer.new.represent(project)
else
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
if result[:status] == :success
render json: ProjectSerializer.new.represent(result[:project])
else
render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
render json: { errors: result[:message] }, status: result[:http_status]
end
end
private
def import_params
params.permit(:repo_id, :new_name, :target_namespace)
end
def client
@client ||= Gitlab::LegacyGithubImport::Client.new(session[access_token_key], client_options)
end
@ -124,10 +117,6 @@ class Import::GithubController < Import::BaseController
{}
end
def extra_project_attrs
{}
end
def extra_import_params
{}
end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
module Import
class BaseService < ::BaseService
def initialize(client, user, params)
@client = client
@current_user = user
@params = params
end
private
def find_or_create_namespace(namespace, owner)
namespace = params[:target_namespace].presence || namespace
return current_user.namespace if namespace == owner
group = Groups::NestedCreateService.new(current_user, group_path: namespace).execute
group.errors.any? ? current_user.namespace : group
rescue => e
Gitlab::AppLogger.error(e)
current_user.namespace
end
def project_save_error(project)
project.errors.full_messages.join(', ')
end
def success(project)
super().merge(project: project, status: :success)
end
end
end

View File

@ -0,0 +1,48 @@
# frozen_string_literal: true
module Import
class GithubService < Import::BaseService
attr_accessor :client
attr_reader :params, :current_user
def execute(access_params, provider)
unless authorized?
return error('This namespace has already been taken! Please choose another one.', :unprocessable_entity)
end
project = Gitlab::LegacyGithubImport::ProjectCreator
.new(repo, project_name, target_namespace, current_user, access_params, type: provider)
.execute(extra_project_attrs)
if project.persisted?
success(project)
else
error(project_save_error(project), :unprocessable_entity)
end
end
def repo
@repo ||= client.repo(params[:repo_id].to_i)
end
def project_name
@project_name ||= params[:new_name].presence || repo.name
end
def namespace_path
@namespace_path ||= params[:target_namespace].presence || current_user.namespace_path
end
def target_namespace
@target_namespace ||= find_or_create_namespace(namespace_path, current_user.namespace_path)
end
def extra_project_attrs
{}
end
def authorized?
can?(current_user, :create_projects, target_namespace)
end
end
end

33
doc/api/import.md Normal file
View File

@ -0,0 +1,33 @@
# Import API
## Import repository from GitHub
Import your projects from GitHub to GitLab via the API.
```
POST /import/github
```
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
| `personal_access_token` | string | yes | GitHub personal access token |
| `repo_id` | integer | yes | GitHub repository ID |
| `new_name` | string | no | New repo name |
| `target_namespace` | string | yes | Namespace to import repo into |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "personal_access_token=abc123&repo_id=12345&target_namespace=root" https://gitlab.example.com/api/v4/import/github
```
Example response:
```json
{
"id": 27,
"name": "my-repo",
"full_path": "/root/my-repo",
"full_name": "Administrator / my-repo"
}
```

View File

@ -110,6 +110,7 @@ module API
mount ::API::GroupMilestones
mount ::API::Groups
mount ::API::GroupVariables
mount ::API::ImportGithub
mount ::API::Internal
mount ::API::Issues
mount ::API::JobArtifacts

46
lib/api/import_github.rb Normal file
View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
module API
class ImportGithub < Grape::API
rescue_from Octokit::Unauthorized, with: :provider_unauthorized
helpers do
def client
@client ||= Gitlab::LegacyGithubImport::Client.new(params[:personal_access_token], client_options)
end
def access_params
{ github_access_token: params[:personal_access_token] }
end
def client_options
{}
end
def provider
:github
end
end
desc 'Import a GitHub project' do
detail 'This feature was introduced in GitLab 11.3.4.'
success Entities::ProjectEntity
end
params do
requires :personal_access_token, type: String, desc: 'GitHub personal access token'
requires :repo_id, type: Integer, desc: 'GitHub repository ID'
optional :new_name, type: String, desc: 'New repo name'
requires :target_namespace, type: String, desc: 'Namespace to import repo into'
end
post 'import/github' do
result = Import::GithubService.new(client, current_user, params).execute(access_params, provider)
if result[:status] == :success
present ProjectSerializer.new.represent(result[:project])
else
status result[:http_status]
{ errors: result[:message] }
end
end
end
end

View File

@ -0,0 +1,56 @@
require 'spec_helper'
describe API::ImportGithub do
include ApiHelpers
let(:token) { "asdasd12345" }
let(:provider) { :github }
let(:access_params) { { github_access_token: token } }
describe "POST /import/github" do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:provider_username) { user.username }
let(:provider_user) { OpenStruct.new(login: provider_username) }
let(:provider_repo) do
OpenStruct.new(
name: 'vim',
full_name: "#{provider_username}/vim",
owner: OpenStruct.new(login: provider_username)
)
end
before do
Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:client).and_return(double('client', user: provider_user, repo: provider_repo).as_null_object)
end
end
it 'returns 201 response when the project is imported successfully' do
allow(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
.and_return(double(execute: project))
post api("/import/github", user), params: {
target_namespace: user.namespace_path,
personal_access_token: token,
repo_id: 1234
}
expect(response).to have_gitlab_http_status(201)
expect(json_response).to be_a Hash
expect(json_response['name']).to eq(project.name)
end
it 'returns 422 response when user can not create projects in the chosen namespace' do
other_namespace = create(:group, name: 'other_namespace')
post api("/import/github", user), params: {
target_namespace: other_namespace.name,
personal_access_token: token,
repo_id: 1234
}
expect(response).to have_gitlab_http_status(422)
end
end
end