Skip to content
Snippets Groups Projects
Commit bd659f70 authored by Sean McGivern's avatar Sean McGivern
Browse files

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
No related branches found
No related tags found
1 merge request!10495Merge Requests - Assignee
Loading
Loading
@@ -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
Loading
Loading
@@ -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)
Loading
Loading
@@ -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?
Loading
Loading
@@ -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
Loading
Loading
@@ -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)
 
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
Loading
@@ -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
 
Loading
Loading
Loading
Loading
@@ -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:
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
@@ -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
}
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment