Commit bd659f70 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'fj-6860-instance-level-project-templates' into 'master'

[CE Port]: Implement instance level project templates

See merge request gitlab-org/gitlab-ce!20761
parents e53e4d45 60943a60
......@@ -2,21 +2,27 @@
module Projects
class CreateFromTemplateService < BaseService
include Gitlab::Utils::StrongMemoize
def initialize(user, params)
@current_user, @params = user, params.dup
end
def execute
template_name = params.delete(:template_name)
file = Gitlab::ProjectTemplate.find(template_name).file
file = Gitlab::ProjectTemplate.find(template_name)&.file
override_params = params.dup
params[:file] = file
GitlabProjectsImportService.new(current_user, params, override_params).execute
ensure
file&.close
end
def template_name
strong_memoize(:template_name) do
params.delete(:template_name).presence
end
end
end
end
......@@ -5,6 +5,9 @@
# The latter will under the hood just import an archive supplied by GitLab.
module Projects
class GitlabProjectsImportService
include Gitlab::Utils::StrongMemoize
include Gitlab::TemplateHelper
attr_reader :current_user, :params
def initialize(user, import_params, override_params = nil)
......@@ -12,47 +15,56 @@ module Projects
end
def execute
FileUtils.mkdir_p(File.dirname(import_upload_path))
prepare_template_environment(template_file&.path)
file = params.delete(:file)
FileUtils.copy_entry(file.path, import_upload_path)
prepare_import_params
@overwrite = params.delete(:overwrite)
data = {}
data[:override_params] = @override_params if @override_params
if overwrite_project?
data[:original_path] = params[:path]
params[:path] += "-#{tmp_filename}"
::Projects::CreateService.new(current_user, params).execute
end
params[:import_type] = 'gitlab_project'
params[:import_source] = import_upload_path
params[:import_data] = { data: data } if data.present?
private
::Projects::CreateService.new(current_user, params).execute
def overwrite_project?
overwrite? && project_with_same_full_path?
end
private
def project_with_same_full_path?
Project.find_by_full_path("#{current_namespace.full_path}/#{params[:path]}").present?
end
def import_upload_path
@import_upload_path ||= Gitlab::ImportExport.import_upload_path(filename: tmp_filename)
def current_namespace
strong_memoize(:current_namespace) do
Namespace.find_by(id: params[:namespace_id])
end
end
def tmp_filename
SecureRandom.hex
def overwrite?
strong_memoize(:overwrite) do
params.delete(:overwrite)
end
end
def overwrite_project?
@overwrite && project_with_same_full_path?
def template_file
strong_memoize(:template_file) do
params.delete(:file)
end
end
def project_with_same_full_path?
Project.find_by_full_path("#{current_namespace.full_path}/#{params[:path]}").present?
def prepare_import_params
data = {}
data[:override_params] = @override_params if @override_params
if overwrite_project?
data[:original_path] = params[:path]
params[:path] += "-#{tmp_filename}"
end
def current_namespace
@current_namespace ||= Namespace.find_by(id: params[:namespace_id])
if template_file
params[:import_type] = 'gitlab_project'
params[:import_source] = import_upload_path
end
params[:import_data] = { data: data } if data.present?
end
end
end
......@@ -7,9 +7,9 @@ class RepositoryImportWorker
include ProjectImportOptions
def perform(project_id)
project = Project.find(project_id)
@project = Project.find(project_id)
return unless start_import(project)
return unless start_import
Gitlab::Metrics.add_event(:import_repository)
......@@ -21,7 +21,7 @@ class RepositoryImportWorker
return if service.async?
if result[:status] == :error
fail_import(project, result[:message]) if project.gitlab_project_import?
fail_import(result[:message]) if template_import?
raise result[:message]
end
......@@ -31,14 +31,20 @@ class RepositoryImportWorker
private
def start_import(project)
attr_reader :project
def start_import
return true if start(project)
Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while importing.")
false
end
def fail_import(project, message)
def fail_import(message)
project.mark_import_as_failed(message)
end
def template_import?
project.gitlab_project_import?
end
end
......@@ -106,6 +106,7 @@ created in snippets, wikis, and repos.
- [Gitaly](gitaly/index.md): Configuring Gitaly, GitLab's Git repository storage service.
- [Default labels](../user/admin_area/labels.html): Create labels that will be automatically added to every new project.
- [Restrict the use of public or internal projects](../public_access/public_access.md#restricting-the-use-of-public-or-internal-projects): Restrict the use of visibility levels for users when they create a project or a snippet.
- [Custom project templates](https://docs.gitlab.com/ee/user/admin_area/custom_project_templates.html): Configure a set of projects to be used as custom templates when creating a new project. **[PREMIUM ONLY]**
### Repository settings
......
......@@ -22,24 +22,28 @@ module Gitlab
class << self
def options
@options ||= Hash[ImportTable.map { |importer| [importer.title, importer.name] }]
Hash[import_table.map { |importer| [importer.title, importer.name] }]
end
def values
@values ||= ImportTable.map(&:name)
import_table.map(&:name)
end
def importer_names
@importer_names ||= ImportTable.select(&:importer).map(&:name)
import_table.select(&:importer).map(&:name)
end
def importer(name)
ImportTable.find { |import_source| import_source.name == name }.importer
import_table.find { |import_source| import_source.name == name }.importer
end
def title(name)
options.key(name)
end
def import_table
ImportTable
end
end
end
end
module Gitlab
module TemplateHelper
include Gitlab::Utils::StrongMemoize
def prepare_template_environment(file_path)
return unless file_path.present?
FileUtils.mkdir_p(File.dirname(import_upload_path))
FileUtils.copy_entry(file_path, import_upload_path)
end
def import_upload_path
strong_memoize(:import_upload_path) do
Gitlab::ImportExport.import_upload_path(filename: tmp_filename)
end
end
def tmp_filename
SecureRandom.hex
end
end
end
......@@ -2,10 +2,11 @@ require 'spec_helper'
describe Projects::CreateFromTemplateService do
let(:user) { create(:user) }
let(:template_name) { 'rails' }
let(:project_params) do
{
path: user.to_param,
template_name: 'rails',
template_name: template_name,
description: 'project description',
visibility_level: Gitlab::VisibilityLevel::PUBLIC
}
......@@ -14,7 +15,10 @@ describe Projects::CreateFromTemplateService do
subject { described_class.new(user, project_params) }
it 'calls the importer service' do
expect_any_instance_of(Projects::GitlabProjectsImportService).to receive(:execute)
import_service_double = double
allow(Projects::GitlabProjectsImportService).to receive(:new).and_return(import_service_double)
expect(import_service_double).to receive(:execute)
subject.execute
end
......@@ -26,6 +30,31 @@ describe Projects::CreateFromTemplateService do
expect(project.import_scheduled?).to be(true)
end
context 'when template is not present' do
let(:template_name) { 'non_existent' }
let(:project) { subject.execute }
before do
expect(project).to be_saved
end
it 'does not set import set import type' do
expect(project.import_type).to be nil
end
it 'does not set import set import source' do
expect(project.import_source).to be nil
end
it 'is not scheduled' do
expect(project.import_scheduled?).to be(false)
end
it 'repository is empty' do
expect(project.repository.empty?).to be(true)
end
end
context 'the result project' do
before do
perform_enqueued_jobs do
......
......@@ -6,60 +6,10 @@ describe Projects::GitlabProjectsImportService do
let(:file) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
let(:overwrite) { false }
let(:import_params) { { namespace_id: namespace.id, path: path, file: file, overwrite: overwrite } }
subject { described_class.new(namespace.owner, import_params) }
describe '#execute' do
context 'with an invalid path' do
let(:path) { '/invalid-path/' }
it 'returns an invalid project' do
project = subject.execute
expect(project).not_to be_persisted
expect(project).not_to be_valid
end
end
context 'with a valid path' do
it 'creates a project' do
project = subject.execute
expect(project).to be_persisted
expect(project).to be_valid
end
end
context 'override params' do
it 'stores them as import data when passed' do
project = described_class
.new(namespace.owner, import_params, description: 'Hello')
.execute
expect(project.import_data.data['override_params']['description']).to eq('Hello')
end
end
context 'when there is a project with the same path' do
let(:existing_project) { create(:project, namespace: namespace) }
let(:path) { existing_project.path}
it 'does not create the project' do
project = subject.execute
expect(project).to be_invalid
expect(project).not_to be_persisted
end
context 'when overwrite param is set' do
let(:overwrite) { true }
it 'creates a project in a temporary full_path' do
project = subject.execute
expect(project).to be_valid
expect(project).to be_persisted
end
end
end
it_behaves_like 'gitlab projects import validations'
end
end
shared_examples 'gitlab projects import validations' do
context 'with an invalid path' do
let(:path) { '/invalid-path/' }
it 'returns an invalid project' do
project = subject.execute
expect(project).not_to be_persisted
expect(project).not_to be_valid
end
end
context 'with a valid path' do
it 'creates a project' do
project = subject.execute
expect(project).to be_persisted
expect(project).to be_valid
end
end
context 'override params' do
it 'stores them as import data when passed' do
project = described_class
.new(namespace.owner, import_params, description: 'Hello')
.execute
expect(project.import_data.data['override_params']['description']).to eq('Hello')
end
end
context 'when there is a project with the same path' do
let(:existing_project) { create(:project, namespace: namespace) }
let(:path) { existing_project.path}
it 'does not create the project' do
project = subject.execute
expect(project).to be_invalid
expect(project).not_to be_persisted
end
context 'when overwrite param is set' do
let(:overwrite) { true }
it 'creates a project in a temporary full_path' do
project = subject.execute
expect(project).to be_valid
expect(project).to be_persisted
end
end
end
end
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