diff options
-rw-r--r-- | app/controllers/projects/pipelines_settings_controller.rb | 2 | ||||
-rw-r--r-- | app/models/ci/pipeline.rb | 10 | ||||
-rw-r--r-- | app/models/project.rb | 13 | ||||
-rw-r--r-- | app/views/projects/pipelines_settings/show.html.haml | 7 | ||||
-rw-r--r-- | db/migrate/20160804142904_add_ci_config_file_to_project.rb | 18 | ||||
-rw-r--r-- | db/schema.rb | 1 | ||||
-rw-r--r-- | doc/api/projects.md | 3 | ||||
-rw-r--r-- | doc/user/project/pipelines/settings.md | 54 | ||||
-rw-r--r-- | lib/api/entities.rb | 1 | ||||
-rw-r--r-- | lib/api/projects.rb | 6 | ||||
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 30 | ||||
-rw-r--r-- | spec/models/project_spec.rb | 1 | ||||
-rw-r--r-- | spec/requests/api/projects_spec.rb | 4 |
13 files changed, 147 insertions, 3 deletions
diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb index 9136633b87a..d23418a9aa3 100644 --- a/app/controllers/projects/pipelines_settings_controller.rb +++ b/app/controllers/projects/pipelines_settings_controller.rb @@ -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 diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2cf9892edc5..e6cd71a7bf2 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -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 diff --git a/app/models/project.rb b/app/models/project.rb index 88e4bd14860..272c89798b6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -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 diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml index 8c7222bfe3d..25a991cdbfc 100644 --- a/app/views/projects/pipelines_settings/show.html.haml +++ b/app/views/projects/pipelines_settings/show.html.haml @@ -33,6 +33,13 @@ = 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 %span.input-group-addon / diff --git a/db/migrate/20160804142904_add_ci_config_file_to_project.rb b/db/migrate/20160804142904_add_ci_config_file_to_project.rb new file mode 100644 index 00000000000..4b9860c5f74 --- /dev/null +++ b/db/migrate/20160804142904_add_ci_config_file_to_project.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 56da70b3c02..26883a72f1f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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 diff --git a/doc/api/projects.md b/doc/api/projects.md index 27436a052da..af229326eee 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -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. diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md new file mode 100644 index 00000000000..272ee71bfed --- /dev/null +++ b/doc/user/project/pipelines/settings.md @@ -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. diff --git a/lib/api/entities.rb b/lib/api/entities.rb index feaa0c213bf..c84a7ef19db 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -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 diff --git a/lib/api/projects.rb b/lib/api/projects.rb index c24e8e8bd9b..291e7b689bf 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -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, diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 550a890797e..8d774666d2b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -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) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8aadfcb439b..363b5ff1913 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -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) } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5f19638b460..80e5deb7f92 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -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) |