Commit 8cb5fe5f authored by Nick Thomas's avatar Nick Thomas 💃🏿

Introduce custom instance-level templates for Dockerfile, .gitignore, and .gitlab-ci.yml files

parent ef5cdecc
class TemplateFinder
prepend ::EE::TemplateFinder
VENDORED_TEMPLATES = {
dockerfiles: ::Gitlab::Template::DockerfileTemplate,
gitignores: ::Gitlab::Template::GitignoreTemplate,
......
......@@ -26,12 +26,28 @@ Templates must be added to a specific subdirectory in the repository,
corresponding to the kind of template. They must also have the correct extension
for the template type.
Currently, only custom license templates are supported. This must go in the
`LICENSE/` subdirectory, and must have `.txt` file extensions. So, the hierarchy
Currently, the following types of custom template are supported:
* `Dockerfile`: `Dockerfile` directory, `.dockerfile` extension
* `.gitignore`: `gitignore` directory, `.gitignore` extension
* `.gitlab-ci.yml`: `gitlab-ci` directory, `.yml` extension
* `LICENSE`: `LICENSE` directory, `.txt` extension
Each template must go in its respective subdirectory and have the correct
extension. So, the hierarchy
should look like this:
```text
|-- README.md
|-- Dockerfile
|-- custom_dockerfile.dockerfile
|-- another_dockerfile.dockerfile
|-- gitignore
|-- custom_gitignore.gitignore
|-- another_gitignore.gitignore
|-- gitlab-ci
|-- custom_gitlab-ci.yml
|-- another_gitlab-ci.yml
|-- LICENSE
|-- custom_license.txt
|-- another_license.txt
......
module EE
module TemplateFinder
include ::Gitlab::Utils::StrongMemoize
extend ::Gitlab::Utils::Override
CUSTOM_TEMPLATES = {
dockerfiles: ::Gitlab::Template::CustomDockerfileTemplate,
gitignores: ::Gitlab::Template::CustomGitignoreTemplate,
gitlab_ci_ymls: ::Gitlab::Template::CustomGitlabCiYmlTemplate
}.freeze
attr_reader :custom_templates
private :custom_templates
def initialize(type, *args, &blk)
super
@custom_templates = CUSTOM_TEMPLATES.fetch(type)
end
override :execute
def execute
return super unless custom_templates?
if params[:name]
find_custom_template || super
else
find_custom_templates + super
end
end
private
def find_custom_template
custom_templates.find(params[:name], template_project)
rescue ::Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
nil
end
def find_custom_templates
custom_templates.all(template_project)
end
def custom_templates?
::License.feature_available?(:custom_file_templates) && template_project.present?
end
def template_project
strong_memoize(:template_project) { ::Gitlab::CurrentSettings.file_template_project }
end
end
end
---
title: Introduce custom instance-level templates for Dockerfile, .gitignore, and .gitlab-ci.yml files
merge_request: 7000
author:
type: added
module Gitlab
module Template
class CustomDockerfileTemplate < CustomTemplate
class << self
def extension
'.dockerfile'
end
def base_dir
'Dockerfile/'
end
end
end
end
end
module Gitlab
module Template
class CustomGitignoreTemplate < CustomTemplate
class << self
def extension
'.gitignore'
end
def base_dir
'gitignore/'
end
end
end
end
end
module Gitlab
module Template
class CustomGitlabCiYmlTemplate < CustomTemplate
class << self
def extension
'.yml'
end
def base_dir
'gitlab-ci/'
end
end
end
end
end
module Gitlab
module Template
class CustomLicenseTemplate < BaseTemplate
class CustomLicenseTemplate < CustomTemplate
class << self
def extension
'.txt'
......@@ -9,10 +9,6 @@ module Gitlab
def base_dir
'LICENSE/'
end
def finder(project)
Gitlab::Template::Finders::RepoTemplateFinder.new(project, self.base_dir, self.extension, self.categories)
end
end
end
end
......
module Gitlab
module Template
class CustomTemplate < BaseTemplate
class << self
def categories
{ 'Custom' => '' }
end
def finder(project)
Gitlab::Template::Finders::RepoTemplateFinder.new(project, self.base_dir, self.extension, self.categories)
end
end
end
end
end
require 'spec_helper'
describe TemplateFinder do
using RSpec::Parameterized::TableSyntax
files = {
'Dockerfile/custom_dockerfile.dockerfile' => 'Custom Dockerfile',
'gitignore/custom_gitignore.gitignore' => 'Custom .gitignore',
'gitlab-ci/custom_gitlab_ci.yml' => 'Custom gitlab-ci.yml'
}
set(:project) { create(:project, :custom_repo, files: files) }
describe '#execute' do
before do
stub_licensed_features(custom_file_templates: true)
stub_ee_application_setting(file_template_project: project)
end
where(:type, :custom_name, :vendored_name) do
:dockerfiles | 'custom_dockerfile' | 'Binary'
:gitignores | 'custom_gitignore' | 'Actionscript'
:gitlab_ci_ymls | 'custom_gitlab_ci' | 'Android'
end
with_them do
subject(:result) { described_class.new(type, params).execute }
context 'specifying name' do
let(:params) { { name: custom_name } }
it { is_expected.to have_attributes(name: custom_name) }
context 'feature is disabled' do
before do
stub_licensed_features(custom_file_templates: false)
end
it { is_expected.to be_nil }
end
end
context 'not specifying name' do
let(:params) { {} }
it { is_expected.to include(have_attributes(name: custom_name)) }
it { is_expected.to include(have_attributes(name: vendored_name)) }
context 'feature is disabled' do
before do
stub_licensed_features(custom_file_templates: false)
end
it { is_expected.not_to include(have_attributes(name: custom_name)) }
it { is_expected.to include(have_attributes(name: vendored_name)) }
end
end
end
end
end
require 'spec_helper'
describe "Custom file template classes" do
files = {
'Dockerfile/foo.dockerfile' => 'CustomDockerfileTemplate Foo',
'Dockerfile/bar.dockerfile' => 'CustomDockerfileTemplate Bar',
'Dockerfile/bad.xyz' => 'CustomDockerfileTemplate Bad',
'gitignore/foo.gitignore' => 'CustomGitignoreTemplate Foo',
'gitignore/bar.gitignore' => 'CustomGitignoreTemplate Bar',
'gitignore/bad.xyz' => 'CustomGitignoreTemplate Bad',
'gitlab-ci/foo.yml' => 'CustomGitlabCiYmlTemplate Foo',
'gitlab-ci/bar.yml' => 'CustomGitlabCiYmlTemplate Bar',
'gitlab-ci/bad.xyz' => 'CustomGitlabCiYmlTemplate Bad',
'LICENSE/foo.txt' => 'CustomLicenseTemplate Foo',
'LICENSE/bar.txt' => 'CustomLicenseTemplate Bar',
'LICENSE/bad.xyz' => 'CustomLicenseTemplate Bad',
'Dockerfile/category/baz.txt' => 'CustomDockerfileTemplate category baz',
'gitignore/category/baz.txt' => 'CustomGitignoreTemplate category baz',
'gitlab-ci/category/baz.yml' => 'CustomGitlabCiYmlTemplate category baz',
'LICENSE/category/baz.txt' => 'CustomLicenseTemplate category baz'
}
let(:project) { create(:project, :custom_repo, files: files) }
[
::Gitlab::Template::CustomDockerfileTemplate,
::Gitlab::Template::CustomGitignoreTemplate,
::Gitlab::Template::CustomGitlabCiYmlTemplate,
::Gitlab::Template::CustomLicenseTemplate
].each do |template_class|
describe template_class do
let(:name) { template_class.name.demodulize }
describe '.all' do
it 'returns all valid templates' do
found = described_class.all(project)
aggregate_failures do
expect(found.map(&:name)).to contain_exactly('foo', 'bar')
expect(found.map(&:category).uniq).to contain_exactly('Custom')
end
end
end
describe '.find' do
let(:not_found_error) { ::Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError }
it 'finds a valid template' do
found = described_class.find('foo', project)
expect(found.name).to eq('foo')
expect(found.content).to eq("#{name} Foo")
end
it 'sets the category correctly' do
pending("#{template_class}.find does not set category correctly")
found = described_class.find('foo', project)
expect(found.category).to eq('Custom')
end
it 'does not find a template with the wrong extension' do
expect { described_class.find('bad', project) }.to raise_error(not_found_error)
end
it 'does not find a template in a subdirectory' do
expect { described_class.find('baz', project) }.to raise_error(not_found_error)
end
end
end
end
end
require 'spec_helper'
describe API::Templates do
files = {
'Dockerfile/custom.dockerfile' => 'Custom dockerfiles',
'gitignore/custom.gitignore' => 'Custom gitignores',
'gitlab-ci/custom.yml' => 'Custom gitlab_ci_ymls',
'LICENSE/custom.txt' => 'Custom licenses'
}
set(:project) { create(:project, :custom_repo, files: files) }
before do
stub_ee_application_setting(file_template_project: project)
end
[
:dockerfiles,
:gitignores,
:gitlab_ci_ymls,
:licenses
].each do |type|
describe "GET /templates/#{type}" do
it 'includes the custom template in the response' do
stub_licensed_features(custom_file_templates: true)
get api("/templates/#{type}")
expect(response).to have_gitlab_http_status(200)
expect(json_response).to satisfy_one { |template| template['name'] == 'custom' }
end
it 'excludes the custom template when the feature is disabled' do
stub_licensed_features(custom_file_templates: false)
get api("/templates/#{type}")
expect(response).to have_gitlab_http_status(200)
expect(json_response).to satisfy_none { |template| template['name'] == 'custom' }
end
end
describe "GET /templates/#{type}/custom" do
it 'returns the custom template' do
stub_licensed_features(custom_file_templates: true)
get api("/templates/#{type}/custom")
expect(response).to have_gitlab_http_status(200)
expect(json_response['name']).to eq('custom')
expect(json_response['content']).to eq("Custom #{type}")
end
it 'returns 404 when the feature is disabled' do
stub_licensed_features(custom_file_templates: false)
get api("/templates/#{type}/custom")
expect(response).to have_gitlab_http_status(404)
end
end
end
end
module Gitlab
module Template
class BaseTemplate
def initialize(path, project = nil)
attr_reader :category
def initialize(path, project = nil, category: nil)
@path = path
@category = category
@finder = self.class.finder(project)
end
def name
File.basename(@path, self.class.extension)
end
alias_method :id, :name
def content
@finder.read(@path)
......@@ -62,7 +66,7 @@ module Gitlab
directory = category_directory(category)
files = finder(project).list_files_for(directory)
files.map { |f| new(f, project) }.sort
files.map { |f| new(f, project, category: category) }.sort
end
def category_directory(category)
......
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