summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/projects/pipelines_settings_controller.rb2
-rw-r--r--app/models/ci/pipeline.rb10
-rw-r--r--app/models/project.rb13
-rw-r--r--app/views/projects/pipelines_settings/show.html.haml7
-rw-r--r--db/migrate/20160804142904_add_ci_config_file_to_project.rb18
-rw-r--r--db/schema.rb1
-rw-r--r--doc/api/projects.md3
-rw-r--r--doc/user/project/pipelines/settings.md54
-rw-r--r--lib/api/entities.rb1
-rw-r--r--lib/api/projects.rb6
-rw-r--r--spec/models/ci/pipeline_spec.rb30
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/requests/api/projects_spec.rb4
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)