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
Pipeline #26938604 passed with stages
in 41 minutes and 44 seconds
......@@ -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,39 +15,17 @@ module Projects
end
def execute
FileUtils.mkdir_p(File.dirname(import_upload_path))
file = params.delete(:file)
FileUtils.copy_entry(file.path, import_upload_path)
@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}"
end
prepare_template_environment(template_file&.path)
params[:import_type] = 'gitlab_project'
params[:import_source] = import_upload_path
params[:import_data] = { data: data } if data.present?
prepare_import_params
::Projects::CreateService.new(current_user, params).execute
end
private
def import_upload_path
@import_upload_path ||= Gitlab::ImportExport.import_upload_path(filename: tmp_filename)
end
def tmp_filename
SecureRandom.hex
end
def overwrite_project?
@overwrite && project_with_same_full_path?
overwrite? && project_with_same_full_path?
end
def project_with_same_full_path?
......@@ -52,7 +33,38 @@ module Projects
end
def current_namespace
@current_namespace ||= Namespace.find_by(id: params[:namespace_id])
strong_memoize(:current_namespace) do
Namespace.find_by(id: params[:namespace_id])
end
end
def overwrite?
strong_memoize(:overwrite) do
params.delete(:overwrite)
end
end
def template_file
strong_memoize(:template_file) do
params.delete(:file)
end
end
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
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
......
......@@ -43,7 +43,7 @@
When you create a new repo locally, instead of going to GitLab to manually
create a new project and then push the repo, you can directly push it to
GitLab to create the new project, all without leaving your terminal. If you have access to that
namespace, we will automatically create a new project under that GitLab namespace with its
namespace, we will automatically create a new project under that GitLab namespace with its
visibility set to Private by default (you can later change it in the [project's settings](../public_access/public_access.md#how-to-change-project-visibility)).
This can be done by using either SSH or HTTP:
......
......@@ -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