Commit 0fcea6b6 authored by Kamil Trzciński's avatar Kamil Trzciński Committed by Rémy Coutable

Merge branch 'feature-ci-only-except-trigger' into 'master'

CI: Add 'triggers' keyword to 'only' and 'except' lists to allow control over when triggers cause builds to run

Currently, the `only` and `except` keywords in `.gitlab-ci.yml` only accept ref names or the special `branches` and `tags` keywords. However, these are primarily useful when controlling how repository activity affects the creation of builds. In my case, instead of building on every commit, I'd like to use the following logic:

- If the repository is tagged, do a build.
- Any other normal commits should not cause a build.
- If a build is triggered via the API, always create one for the specified ref.

From what I can tell, this isn't possible via the existing YAML syntax. In this MR, I introduce a new keyword `triggers` that goes along with `branches` and `tags`. I can implement the logic above using the following job configuration:

```yaml
only:
  - tags
  - triggers
```

I updated the tests and documentation to reflect this and everything seems to pass.

See merge request !3230
parent 9684d7fd
module Ci module Ci
class CreateBuildsService class CreateBuildsService
def execute(commit, stage, ref, tag, user, trigger_request, status) def execute(commit, stage, ref, tag, user, trigger_request, status)
builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag) builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag, trigger_request)
# check when to create next build # check when to create next build
builds_attrs = builds_attrs.select do |build_attrs| builds_attrs = builds_attrs.select do |build_attrs|
......
...@@ -331,7 +331,7 @@ There are a few rules that apply to the usage of refs policy: ...@@ -331,7 +331,7 @@ There are a few rules that apply to the usage of refs policy:
* `only` and `except` are inclusive. If both `only` and `except` are defined * `only` and `except` are inclusive. If both `only` and `except` are defined
in a job specification, the ref is filtered by `only` and `except`. in a job specification, the ref is filtered by `only` and `except`.
* `only` and `except` allow the use of regular expressions. * `only` and `except` allow the use of regular expressions.
* `only` and `except` allow the use of special keywords: `branches` and `tags`. * `only` and `except` allow the use of special keywords: `branches`, `tags`, and `triggers`.
* `only` and `except` allow to specify a repository path to filter jobs for * `only` and `except` allow to specify a repository path to filter jobs for
forks. forks.
...@@ -348,6 +348,17 @@ job: ...@@ -348,6 +348,17 @@ job:
- branches - branches
``` ```
In this example, `job` will run only for refs that are tagged, or if a build is explicitly requested
via an API trigger.
```yaml
job:
# use special keywords
only:
- tags
- triggers
```
The repository path can be used to have jobs executed only for the parent The repository path can be used to have jobs executed only for the parent
repository and not forks: repository and not forks:
......
...@@ -26,8 +26,8 @@ module Ci ...@@ -26,8 +26,8 @@ module Ci
validate! validate!
end end
def builds_for_stage_and_ref(stage, ref, tag = false) def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
builds.select{|build| build[:stage] == stage && process?(build[:only], build[:except], ref, tag)} builds.select{|build| build[:stage] == stage && process?(build[:only], build[:except], ref, tag, trigger_request)}
end end
def builds def builds
...@@ -266,29 +266,30 @@ module Ci ...@@ -266,29 +266,30 @@ module Ci
value.in?([true, false]) value.in?([true, false])
end end
def process?(only_params, except_params, ref, tag) def process?(only_params, except_params, ref, tag, trigger_request)
if only_params.present? if only_params.present?
return false unless matching?(only_params, ref, tag) return false unless matching?(only_params, ref, tag, trigger_request)
end end
if except_params.present? if except_params.present?
return false if matching?(except_params, ref, tag) return false if matching?(except_params, ref, tag, trigger_request)
end end
true true
end end
def matching?(patterns, ref, tag) def matching?(patterns, ref, tag, trigger_request)
patterns.any? do |pattern| patterns.any? do |pattern|
match_ref?(pattern, ref, tag) match_ref?(pattern, ref, tag, trigger_request)
end end
end end
def match_ref?(pattern, ref, tag) def match_ref?(pattern, ref, tag, trigger_request)
pattern, path = pattern.split('@', 2) pattern, path = pattern.split('@', 2)
return false if path && path != self.path return false if path && path != self.path
return true if tag && pattern == 'tags' return true if tag && pattern == 'tags'
return true if !tag && pattern == 'branches' return true if !tag && pattern == 'branches'
return true if trigger_request.present? && pattern == 'triggers'
if pattern.first == "/" && pattern.last == "/" if pattern.first == "/" && pattern.last == "/"
Regexp.new(pattern[1...-1]) =~ ref Regexp.new(pattern[1...-1]) =~ ref
......
...@@ -97,6 +97,28 @@ module Ci ...@@ -97,6 +97,28 @@ module Ci
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0) expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
end end
it "returns builds if only has a triggers keyword specified and a trigger is provided" do
config = YAML.dump({
before_script: ["pwd"],
rspec: { script: "rspec", type: type, only: ["triggers"] }
})
config_processor = GitlabCiYamlProcessor.new(config, path)
expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, true).size).to eq(1)
end
it "does not return builds if only has a triggers keyword specified and no trigger is provided" do
config = YAML.dump({
before_script: ["pwd"],
rspec: { script: "rspec", type: type, only: ["triggers"] }
})
config_processor = GitlabCiYamlProcessor.new(config, path)
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
end
it "returns builds if only has current repository path" do it "returns builds if only has current repository path" do
config = YAML.dump({ config = YAML.dump({
before_script: ["pwd"], before_script: ["pwd"],
...@@ -203,6 +225,28 @@ module Ci ...@@ -203,6 +225,28 @@ module Ci
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1) expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
end end
it "does not return builds if except has a triggers keyword specified and a trigger is provided" do
config = YAML.dump({
before_script: ["pwd"],
rspec: { script: "rspec", type: type, except: ["triggers"] }
})
config_processor = GitlabCiYamlProcessor.new(config, path)
expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, true).size).to eq(0)
end
it "returns builds if except has a triggers keyword specified and no trigger is provided" do
config = YAML.dump({
before_script: ["pwd"],
rspec: { script: "rspec", type: type, except: ["triggers"] }
})
config_processor = GitlabCiYamlProcessor.new(config, path)
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
end
it "does not return builds if except has current repository path" do it "does not return builds if except has current repository path" do
config = YAML.dump({ config = YAML.dump({
before_script: ["pwd"], before_script: ["pwd"],
......
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