diff --git a/doc/api/import.md b/doc/api/import.md index 261340e6924ec6cd3a60f8cc52c57c0f9ead2fe1..7a045ee8b30702c94630a4509015ac38cb6983c1 100644 --- a/doc/api/import.md +++ b/doc/api/import.md @@ -223,6 +223,46 @@ curl --request POST \ }' ``` +## Import a repository from Gitea + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/442717) in GitLab 16.11. + +Import your projects from Gitea Server to GitLab using the API. + +Prerequisites: + +- [Prerequisites for Gitea importer](../user/project/import/gitea.md#prerequisites). +- The namespace set in `target_namespace` must exist. +- The namespace can be your user namespace or an existing group that you have at least the Maintainer role for. + +```plaintext +POST /import/gitea +``` + +| Attribute | Type | Required | Description | +|----------------------------|---------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `personal_access_token` | string | yes | Gitea personal access token | +| `repo_id` | integer | yes | Gitea repository ID | +| `new_name` | string | no | New repository name | +| `target_namespace` | string | yes | Namespace to import repository into. Supports subgroups like `/namespace/subgroup`. Must not be blank | +| `gitea_host_url` | string | no | Gitea host URL | +| `optional_stages` | object | no | [Additional items to import](../user/project/import/github.md#select-additional-items-to-import). | +| `timeout_strategy` | string | no | Strategy for handling import timeouts. Valid values are `optimistic` (continue to next stage of import) or `pessimistic` (fail immediately). Defaults to `pessimistic`. | + +```shell +curl --request POST \ + --url "https://gitlab.example.com/api/v4/import/gitea" \ + --header "content-type: application/json" \ + --header "Authorization: Bearer " \ + --data '{ + "personal_access_token": "aBc123abC12aBc123abC12abC123+_A/c123", + "repo_id": "12345", + "target_namespace": "group/subgroup", + "new_name": "NEW-NAME", + "gitea_host_url": "https://mygitea.example.com" +}' +``` + ## Related topics - [Group migration by direct transfer API](bulk_imports.md). diff --git a/lib/api/api.rb b/lib/api/api.rb index cecb0d9001ebed0f73fce3ea6270f996143883e8..9a9309224310c72c5790b0f30774755a2ddeb74a 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -267,6 +267,7 @@ def initialize(location_url) mount ::API::HelmPackages mount ::API::ImportBitbucketServer mount ::API::ImportGithub + mount ::API::ImportGitea mount ::API::Integrations mount ::API::Integrations::Slack::Events mount ::API::Integrations::Slack::Interactions diff --git a/lib/api/helpers/import_gitea_helpers.rb b/lib/api/helpers/import_gitea_helpers.rb new file mode 100644 index 0000000000000000000000000000000000000000..014c4611cbd45a443edddeaa680fa8aba979ee59 --- /dev/null +++ b/lib/api/helpers/import_gitea_helpers.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module API + module Helpers + module ImportGiteaHelpers + 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 provider + :gitea + end + + def provider_name + :gitea + end + + def provider_url + params[:gitea_host_url] + end + + def client_options + verified_url, provider_hostname = verify_blocked_uri + + { + host: verified_url.scheme == 'https' ? provider_url : verified_url.to_s, + api_version: 'v1', + hostname: provider_hostname + } + end + + def verify_blocked_uri + @verify_blocked_uri ||= Gitlab::UrlBlocker.validate!( + provider_url, + allow_localhost: allow_local_requests?, + allow_local_network: allow_local_requests?, + schemes: %w[http https] + ) + rescue Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError => e + error!(format(s_('Specified URL cannot be used: "%{reason}"'), reason: e.message), 403) + end + + def allow_local_requests? + Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services? + end + + def provider_unauthorized + error!("Access denied to your #{Gitlab::ImportSources.title(provider.to_s)} account.", 401) + end + + def too_many_requests + error!('Too Many Requests', 429) + end + end + end +end diff --git a/lib/api/import_gitea.rb b/lib/api/import_gitea.rb new file mode 100644 index 0000000000000000000000000000000000000000..1d55dbb9fd76dc1e0d8eb1c518a9a70e2476f32c --- /dev/null +++ b/lib/api/import_gitea.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module API + class ImportGitea < ::API::ImportGithub + extend ::Gitlab::Utils::Override + before { authenticate! } + + helpers ::API::Helpers::ImportGiteaHelpers + + desc 'Import a Gitea project' do + detail 'This feature is experimental' + success code: 201, model: ::ProjectEntity + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 422, message: 'Unprocessable entity' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_import_gitea'] + end + params do + requires :personal_access_token, type: String, desc: 'Gitea personal access token' + requires :repo_id, type: Integer, desc: 'Gitea repository ID' + optional :new_name, type: String, desc: 'New repo name' + requires :target_namespace, type: String, allow_blank: false, desc: 'Namespace or group to import repository into' + optional :gitea_host_url, type: String, desc: 'Gitea Host url' + optional :optional_stages, type: Hash, desc: 'Optional stages of import to be performed' + optional :timeout_strategy, type: String, values: ::ProjectImportData::TIMEOUT_STRATEGIES, + desc: 'Strategy for behavior on timeouts' + end + post 'import/gitea' 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