Commit dccf61de authored by Stan Hu's avatar Stan Hu

Support creating project from template via API

This adds support for creating projects from instance/project/group
templates via the API.

Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/19497
parent 7540c54c
......@@ -991,6 +991,9 @@ POST /projects/user/:user_id
| `external_authorization_classification_label` | string | no | **(PREMIUM)** The classification label for the project |
| `mirror` | boolean | no | **(STARTER)** Enables pull mirroring in a project |
| `mirror_trigger_builds` | boolean | no | **(STARTER)** Pull mirroring triggers builds |
| `template_name` | string | no | When used without `use_custom_template`, name of a [built-in project template](../gitlab-basics/create-project.md#built-in-templates). When used with `use_custom_template`, name of a custom project template |
| `use_custom_template` | boolean | no | **(PREMIUM)** Use either custom [instance](../user/admin_area/custom_project_templates.md) or [group](../user/group/custom_project_templates.md) (with `group_with_project_templates_id`) project template |
| `group_with_project_templates_id` | integer | no | **(PREMIUM)** For group-level custom templates, specifies ID of group from which all the custom project templates are sourced. Leave empty for instance-level templates. Requires `use_custom_template` to be true |
NOTE: **Note:** If your HTTP repository is not publicly accessible,
add authentication information to the URL: `https://username:password@gitlab.company.com/group/project.git`
......
---
title: Support creating project from template via API
merge_request: 16352
author:
type: added
......@@ -8,6 +8,13 @@ module EE
extend ::Gitlab::Utils::Override
prepended do
params :optional_create_project_params_ee do
optional :use_custom_template, type: Grape::API::Boolean, desc: "Use custom template"
given :use_custom_template do
optional :group_with_project_templates_id, type: Integer, desc: "Group ID that serves as the template source"
end
end
params :optional_project_params_ee do
optional :repository_storage, type: String, desc: 'Which storage shard the repository is on. Available only to admins'
optional :approvals_before_merge, type: Integer, desc: 'How many approvers should approve merge request by default'
......
......@@ -183,6 +183,65 @@ describe API::Projects do
end
describe 'POST /projects' do
shared_examples 'creates projects with templates' do
before do
group.add_maintainer(user)
stub_licensed_features(custom_project_templates: true)
stub_ee_application_setting(custom_project_templates_group_id: group.id)
end
it 'creates a project using a template' do
expect(ProjectExportWorker).to receive(:perform_async).and_call_original
Sidekiq::Testing.fake! do
expect { post api('/projects', user), params: project_params }
.to change { Project.count }.by(1)
end
expect(response).to have_gitlab_http_status(201)
project = Project.find(json_response['id'])
expect(project.name).to eq(new_project_name)
end
end
context 'with instance-level templates' do
let(:group) { create(:group) }
let!(:project) { create(:project, :public, namespace: group) }
let(:new_project_name) { "project-#{SecureRandom.hex}" }
let(:project_params) do
{
template_name: project.name,
name: new_project_name,
path: new_project_name,
use_custom_template: true,
namespace_id: group.id
}
end
it_behaves_like 'creates projects with templates'
end
context 'with group templates' do
let(:parent_group) { create(:group) }
let(:subgroup) { create(:group, :public, parent: parent_group) }
let(:group) { subgroup }
let!(:project) { create(:project, :public, namespace: subgroup) }
let(:new_project_name) { "project-#{SecureRandom.hex}" }
let(:project_params) do
{
template_name: project.name,
name: new_project_name,
path: new_project_name,
use_custom_template: true,
group_with_project_templates_id: subgroup.id,
namespace_id: subgroup.id
}
end
it_behaves_like 'creates projects with templates'
end
context 'when importing with mirror attributes' do
let(:import_url) { generate(:url) }
let(:mirror_params) do
......
......@@ -56,6 +56,14 @@ module API
use :optional_project_params_ee
end
params :optional_create_project_params_ee do
end
params :optional_create_project_params do
use :optional_project_params
use :optional_create_project_params_ee
end
params :optional_filter_params_ee do
end
......
......@@ -68,6 +68,8 @@ module API
params :create_params do
optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.'
optional :import_url, type: String, desc: 'URL from which the project is imported'
optional :template_name, type: String, desc: "Name of template from which to create project"
mutually_exclusive :import_url, :template_name
end
def load_projects
......@@ -155,7 +157,7 @@ module API
optional :name, type: String, desc: 'The name of the project'
optional :path, type: String, desc: 'The path of the repository'
at_least_one_of :name, :path
use :optional_project_params
use :optional_create_project_params
use :create_params
end
post do
......
......@@ -631,6 +631,25 @@ describe API::Projects do
expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED)
end
it 'creates a project using a template' do
expect { post api('/projects', user), params: { template_name: 'rails', name: 'rails-test' } }
.to change { Project.count }.by(1)
expect(response).to have_gitlab_http_status(201)
project = Project.find(json_response['id'])
expect(project).to be_saved
expect(project.import_type).to eq('gitlab_project')
end
it 'disallows creating a project with an import_url and template' do
project_params = { import_url: 'http://example.com', template_name: 'rails', name: 'rails-test' }
expect { post api('/projects', user), params: project_params }
.not_to change { Project.count }
expect(response).to have_gitlab_http_status(400)
end
it 'sets a project as public' do
project = attributes_for(:project, visibility: 'public')
......@@ -867,7 +886,7 @@ describe API::Projects do
expect { post api("/projects/user/#{user.id}", admin), params: { name: 'Foo Project' } }.to change { Project.count }.by(1)
expect(response).to have_gitlab_http_status(201)
project = Project.last
project = Project.find(json_response['id'])
expect(project.name).to eq('Foo Project')
expect(project.path).to eq('foo-project')
......@@ -878,7 +897,7 @@ describe API::Projects do
.to change { Project.count }.by(1)
expect(response).to have_gitlab_http_status(201)
project = Project.last
project = Project.find(json_response['id'])
expect(project.name).to eq('Foo Project')
expect(project.path).to eq('path-project-Foo')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment