Commit 07365e51 authored by Keith Pope's avatar Keith Pope
Browse files

Add config option to project to allow custom .gitlab-ci.yml location

parent 26b19761ea86
......@@ -30,7 +30,7 @@ def create_params
def update_params
params.require(:project).permit(
:runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds
:public_builds, :ci_config_file
)
end
end
......@@ -218,14 +218,22 @@ def ci_yaml_file
return @ci_yaml_file if defined?(@ci_yaml_file)
@ci_yaml_file ||= begin
blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
blob = project.repository.blob_at(sha, ci_yaml_file_path)
blob.load_all_data!(project.repository)
blob.data
rescue
self.yaml_errors = 'Failed to load CI config file'
nil
end
end
def ci_yaml_file_path
return '.gitlab-ci.yml' if project.ci_config_file.blank?
return project.ci_config_file if File.extname(project.ci_config_file.to_s) == '.yml'
File.join(project.ci_config_file || '', '.gitlab-ci.yml')
end
def environments
builds.where.not(environment: nil).success.pluck(:environment).uniq
end
......
......@@ -154,6 +154,11 @@ def update_forks_visibility_level
# Validations
validates :creator, presence: true, on: :create
validates :description, length: { maximum: 2000 }, allow_blank: true
validates :ci_config_file,
format: { without: Gitlab::Regex.directory_traversal_regex,
message: Gitlab::Regex.directory_traversal_regex_message },
length: { maximum: 255 },
allow_blank: true
validates :name,
presence: true,
length: { within: 0..255 },
......@@ -182,6 +187,7 @@ def update_forks_visibility_level
add_authentication_token_field :runners_token
before_save :ensure_runners_token
before_validation :clean_ci_config_file
mount_uploader :avatar, AvatarUploader
......@@ -986,6 +992,7 @@ def hook_attrs(backward: true)
visibility_level: visibility_level,
path_with_namespace: path_with_namespace,
default_branch: default_branch,
ci_config_file: ci_config_file
}
# Backward compatibility
......@@ -1349,4 +1356,10 @@ def authorized_for_user_by_shared_projects?(user, min_access_level)
shared_projects.any?
end
def clean_ci_config_file
return unless self.ci_config_file
# Cleanup path removing leading/trailing slashes
self.ci_config_file = ci_config_file.gsub(/^\/+|\/+$/, '')
end
end
......@@ -32,6 +32,13 @@
= f.label :build_timeout_in_minutes, 'Timeout', class: 'label-light'
= f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
%p.help-block per build in minutes
.form-group
= f.label :ci_config_file, 'Custom CI Config File', class: 'label-light'
= f.text_field :ci_config_file, class: 'form-control', placeholder: '.gitlab-ci.yml'
%p.help-block
Optionally specify the location of your CI config file E.g. my/path or my/path/.my-config.yml.
Default is to use '.gitlab-ci.yml' in the repository root.
.form-group
= f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light'
.input-group
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddCiConfigFileToProject < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def change
add_column :projects, :ci_config_file, :string
end
def down
remove_column :projects, :ci_config_file
end
end
......@@ -889,6 +889,7 @@
t.boolean "has_external_wiki"
t.boolean "lfs_enabled"
t.text "description_html"
t.string "ci_config_file"
end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
......
......@@ -604,6 +604,7 @@ Parameters:
| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
| `ci_config_file` | boolean | no | The relative path to the CI config file (E.g. my/path or my/path/.gitlab-ci.yml or my/path/my-config.yml) |
### Create project for user
......@@ -636,6 +637,7 @@ Parameters:
| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
| `ci_config_file` | boolean | no | The relative path to the CI config file (E.g. my/path or my/path/.gitlab-ci.yml or my/path/my-config.yml) |
### Edit project
......@@ -667,6 +669,7 @@ Parameters:
| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
| `ci_config_file` | boolean | no | The relative path to the CI config file (E.g. my/path or my/path/.gitlab-ci.yml or my/path/my-config.yml) |
On success, method returns 200 with the updated project. If parameters are
invalid, 400 is returned.
......
# Project Pipeline Settings
This section covers project level pipeline settings.
## Clone vs Fetch
You can select to either `git fetch` or `git clone` your project before
each build. Fetching is faster as you are only pulling recent updates
but cloning has the advantage of giving you a clean project.
## Timeout
This is the total time in minutes that a build is allowed to run. The
default is 222 minutes.
## Custom CI Config File
> - [Introduced][ce-15041] in GitLab 8.13.
By default we look for the `.gitlab-ci.yml` file in the projects root
directory. If you require a different location **within** the repository
you can set a custom filepath that will be used to lookup the config file,
this filepath should be **relative** to the root.
Here are some valid examples:
> * .gitlab-ci.yml
> * .my-custom-file.yml
> * my/path/.gitlab-ci.yml
> * my/path/.my-custom-file.yml
## Test Coverage Parsing
As each testing framework has different output, you need to specify a
regex to extract the summary code coverage information from your test
commands output. The regex will be applied to the `STDOUT` of your command.
Here are some examples of popular testing frameworks/languages:
> * Simplecov (Ruby) - `\(\d+.\d+\%\) covered`
> * pytest-cov (Python) - `\d+\%\s*$`
> * phpunit --coverage-text --colors=never (PHP) - `^\s*Lines:\s*\d+.\d+\%`
> * gcovr (C/C++) - `^TOTAL.*\s+(\d+\%)$`
> * tap --coverage-report=text-summary (Node.js) - `^Statements\s*:\s*([^%]+)`
## Public Pipelines
You can select if the pipeline should be publicly accessible or not.
## Runners Token
This is a secure token that is used to checkout the project from the
Gitlab instance. This should be a cryptographically secure random hash.
......@@ -96,6 +96,7 @@ class Project < Grape::Entity
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:user]) && project.default_issues_tracker? }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :public_builds
expose :ci_config_file
expose :shared_with_groups do |project, options|
SharedGroup.represent(project.project_group_links.all, options)
end
......
......@@ -118,12 +118,14 @@ def map_public_to_visibility_level(attrs)
# public_builds (optional)
# lfs_enabled (optional)
# request_access_enabled (optional) - Allow users to request member access
# ci_config_file (optional)
# Example Request
# POST /projects
post do
required_attributes! [:name]
attrs = attributes_for_keys [:builds_enabled,
:container_registry_enabled,
:ci_config_file,
:description,
:import_url,
:issues_enabled,
......@@ -173,12 +175,14 @@ def map_public_to_visibility_level(attrs)
# public_builds (optional)
# lfs_enabled (optional)
# request_access_enabled (optional) - Allow users to request member access
# ci_config_file (optional)
# Example Request
# POST /projects/user/:user_id
post "user/:user_id" do
authenticated_as_admin!
user = User.find(params[:user_id])
attrs = attributes_for_keys [:builds_enabled,
:ci_config_file,
:default_branch,
:description,
:import_url,
......@@ -256,11 +260,13 @@ def map_public_to_visibility_level(attrs)
# visibility_level (optional) - visibility level of a project
# public_builds (optional)
# lfs_enabled (optional)
# ci_config_file (optional)
# Example Request
# PUT /projects/:id
put ':id' do
attrs = attributes_for_keys [:builds_enabled,
:container_registry_enabled,
:ci_config_file,
:default_branch,
:description,
:issues_enabled,
......
......@@ -403,6 +403,36 @@ def create_build(name, queued_at = current, started_from = 0)
end
end
describe 'yaml config file resolution' do
let(:project) { FactoryGirl.build(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
it 'uses custom ci config file path when present' do
allow(project).to receive(:ci_config_file) { 'custom/path' }
expect(pipeline.ci_yaml_file_path).to eq('custom/path/.gitlab-ci.yml')
end
it 'uses root when custom path is nil' do
allow(project).to receive(:ci_config_file) { nil }
expect(pipeline.ci_yaml_file_path).to eq('.gitlab-ci.yml')
end
it 'uses root when custom path is empty' do
allow(project).to receive(:ci_config_file) { '' }
expect(pipeline.ci_yaml_file_path).to eq('.gitlab-ci.yml')
end
it 'allows custom filename' do
allow(project).to receive(:ci_config_file) { 'custom/path/.my-config.yml' }
expect(pipeline.ci_yaml_file_path).to eq('custom/path/.my-config.yml')
end
it 'custom filename must be yml' do
allow(project).to receive(:ci_config_file) { 'custom/path/.my-config.cnf' }
expect(pipeline.ci_yaml_file_path).to eq('custom/path/.my-config.cnf/.gitlab-ci.yml')
end
it 'reports error if the file is not found' do
pipeline.ci_yaml_file
expect(pipeline.yaml_errors).to eq('Failed to load CI config file')
end
end
describe '#execute_hooks' do
let!(:build_a) { create_build('a', 0) }
let!(:build_b) { create_build('b', 1) }
......
......@@ -118,6 +118,7 @@
it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) }
it { is_expected.to validate_length_of(:path).is_within(0..255) }
it { is_expected.to validate_length_of(:description).is_within(0..2000) }
it { is_expected.to validate_length_of(:ci_config_file).is_within(0..255) }
it { is_expected.to validate_presence_of(:creator) }
it { is_expected.to validate_presence_of(:namespace) }
it { is_expected.to validate_presence_of(:repository_storage) }
......
......@@ -256,7 +256,8 @@
merge_requests_enabled: false,
wiki_enabled: false,
only_allow_merge_if_build_succeeds: false,
request_access_enabled: true
request_access_enabled: true,
ci_config_file: 'a/custom/path'
})
post api('/projects', user), project
......@@ -503,6 +504,7 @@
expect(json_response['star_count']).to be_present
expect(json_response['forks_count']).to be_present
expect(json_response['public_builds']).to be_present
expect(json_response['ci_config_file']).to be_nil
expect(json_response['shared_with_groups']).to be_an Array
expect(json_response['shared_with_groups'].length).to eq(1)
expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
......
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