Add config option to project to allow custom .gitlab-ci.yml location
This commit is contained in:
parent
28ca8502c2
commit
07365e5183
|
@ -30,7 +30,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
|
|||
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 @@ module Ci
|
|||
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 @@ class Project < ActiveRecord::Base
|
|||
# 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 @@ class Project < ActiveRecord::Base
|
|||
|
||||
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 @@ class Project < ActiveRecord::Base
|
|||
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 @@ class Project < ActiveRecord::Base
|
|||
|
||||
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
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# 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 @@ ActiveRecord::Schema.define(version: 20160926145521) do
|
|||
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.
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
# 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 @@ module API
|
|||
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 @@ module API
|
|||
# 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 @@ module API
|
|||
# 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 @@ module API
|
|||
# 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 @@ describe Ci::Pipeline, models: true do
|
|||
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 @@ describe Project, models: true do
|
|||
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 @@ describe API::API, api: true do
|
|||
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 @@ describe API::API, api: true do
|
|||
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)
|
||||
|
|
Loading…
Reference in New Issue