From bb41da7f15b72eb6d94dd0670efcc8424d2b5c9b Mon Sep 17 00:00:00 2001 From: lma-git Date: Tue, 6 Jun 2023 16:14:30 -0700 Subject: [PATCH 1/4] Add 'when: never' support to include:rules Added support for 'when: never' on conditional includes. The file is not added to the pipeline if no rules match or if a rule matches and has 'when: never'. --- app/assets/javascripts/editor/schema/ci.json | 62 +++++++++++++++- .../ci_support_include_rules_when_never.yml | 8 +++ doc/ci/yaml/includes.md | 10 +++ .../ci/config/entry/include/rules/rule.rb | 5 +- lib/gitlab/ci/config/external/rules.rb | 27 ++++++- .../ci/yaml_tests/negative_tests/include.yml | 7 ++ .../ci/yaml_tests/positive_tests/include.yml | 4 +- .../config/entry/include/rules/rule_spec.rb | 16 ++++- .../ci/config/external/mapper/filter_spec.rb | 14 ++++ .../gitlab/ci/config/external/rules_spec.rb | 71 ++++++++++++++++--- 10 files changed, 207 insertions(+), 17 deletions(-) create mode 100644 config/feature_flags/development/ci_support_include_rules_when_never.yml diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index 82a8cfaa3ebbe6..8e307bc1f19a4c 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -359,7 +359,7 @@ "pattern": "\\.ya?ml$" }, "rules": { - "$ref": "#/definitions/rules" + "$ref": "#/definitions/includeRules" }, "inputs": { "$ref": "#/definitions/inputs" @@ -399,6 +399,9 @@ } ] }, + "rules": { + "$ref": "#/definitions/includeRules" + }, "inputs": { "$ref": "#/definitions/inputs" } @@ -418,6 +421,9 @@ "format": "uri-reference", "pattern": "\\.ya?ml$" }, + "rules": { + "$ref": "#/definitions/includeRules" + }, "inputs": { "$ref": "#/definitions/inputs" } @@ -435,6 +441,9 @@ "type": "string", "format": "uri-reference" }, + "rules": { + "$ref": "#/definitions/includeRules" + }, "inputs": { "$ref": "#/definitions/inputs" } @@ -453,6 +462,9 @@ "format": "uri-reference", "pattern": "^https?://.+\\.ya?ml$" }, + "rules": { + "$ref": "#/definitions/includeRules" + }, "inputs": { "$ref": "#/definitions/inputs" } @@ -794,6 +806,54 @@ ] } }, + "includeRules": { + "type": [ + "array", + "null" + ], + "markdownDescription": "You can use rules to conditionally include other configuration files. [Learn More](https://docs.gitlab.com/ee/ci/yaml/includes.html#use-rules-with-include).", + "items": { + "anyOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "if": { + "$ref": "#/definitions/if" + }, + "exists": { + "$ref": "#/definitions/exists" + }, + "when": { + "markdownDescription": "Use `when: never` to exclude the configuration file if the condition matches. [Learn More](https://docs.gitlab.com/ee/ci/yaml/includes.html#include-with-rulesif).", + "oneOf": [ + { + "type": "string", + "enum": [ + "never" + ] + }, + { + "type": "null" + } + ] + } + } + }, + { + "type": "string", + "minLength": 1 + }, + { + "type": "array", + "minLength": 1, + "items": { + "type": "string" + } + } + ] + } + }, "workflowName": { "type": "string", "markdownDescription": "Defines the pipeline name. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#workflowname).", diff --git a/config/feature_flags/development/ci_support_include_rules_when_never.yml b/config/feature_flags/development/ci_support_include_rules_when_never.yml new file mode 100644 index 00000000000000..594da30ec971f0 --- /dev/null +++ b/config/feature_flags/development/ci_support_include_rules_when_never.yml @@ -0,0 +1,8 @@ +--- +name: ci_support_include_rules_when_never +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122810 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414517 +milestone: '16.1' +type: development +group: group::pipeline authoring +default_enabled: false diff --git a/doc/ci/yaml/includes.md b/doc/ci/yaml/includes.md index 35b302d03734d4..37dbafb5684115 100644 --- a/doc/ci/yaml/includes.md +++ b/doc/ci/yaml/includes.md @@ -435,6 +435,16 @@ test: script: exit 0 ``` +Use `when: never` to exclude the configuration file if the condition matches. For example: + +```yaml +include: + - local: builds.yml + rules: + - if: $INCLUDE_BUILDS == "false" + when: never +``` + ### `include` with `rules:exists` Use [`rules:exists`](index.md#rulesexists) to conditionally include other configuration files diff --git a/lib/gitlab/ci/config/entry/include/rules/rule.rb b/lib/gitlab/ci/config/entry/include/rules/rule.rb index fa99a7204d6ba3..28e76454647b5a 100644 --- a/lib/gitlab/ci/config/entry/include/rules/rule.rb +++ b/lib/gitlab/ci/config/entry/include/rules/rule.rb @@ -9,9 +9,10 @@ class Rules::Rule < ::Gitlab::Config::Entry::Node include ::Gitlab::Config::Entry::Validatable include ::Gitlab::Config::Entry::Attributable - ALLOWED_KEYS = %i[if exists].freeze + ALLOWED_KEYS = %i[if exists when].freeze + ALLOWED_WHEN = %w[never].freeze - attributes :if, :exists + attributes :if, :exists, :when validations do validates :config, presence: true diff --git a/lib/gitlab/ci/config/external/rules.rb b/lib/gitlab/ci/config/external/rules.rb index 95470537de3b95..8aaa14b3d7bd17 100644 --- a/lib/gitlab/ci/config/external/rules.rb +++ b/lib/gitlab/ci/config/external/rules.rb @@ -6,6 +6,7 @@ class Config module External class Rules ALLOWED_KEYS = Entry::Include::Rules::Rule::ALLOWED_KEYS + ALLOWED_WHEN = Entry::Include::Rules::Rule::ALLOWED_WHEN InvalidIncludeRulesError = Class.new(Mapper::Error) @@ -16,7 +17,17 @@ def initialize(rule_hashes) end def evaluate(context) - Result.new(@rule_list.nil? || match_rule(context)) + if Feature.enabled?(:ci_support_include_rules_when_never, context.project) + if @rule_list.nil? + Result.new(nil) + elsif matched_rule = match_rule(context) + Result.new(matched_rule.attributes[:when]) + else + Result.new('never') + end + else + LegacyResult.new(@rule_list.nil? || match_rule(context)) + end end private @@ -29,13 +40,23 @@ def validate(rule_hashes) return unless rule_hashes.is_a?(Array) rule_hashes.each do |rule_hash| - next if (rule_hash.keys - ALLOWED_KEYS).empty? + next if (rule_hash.keys - ALLOWED_KEYS).empty? && valid_when?(rule_hash) raise InvalidIncludeRulesError, "invalid include rule: #{rule_hash}" end end - Result = Struct.new(:result) do + def valid_when?(rule_hash) + rule_hash[:when].nil? || rule_hash[:when].in?(ALLOWED_WHEN) + end + + Result = Struct.new(:when) do + def pass? + self.when != 'never' + end + end + + LegacyResult = Struct.new(:result) do def pass? !!result end diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/include.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/include.yml index 6afd8baa0e8043..56941fcc6d5868 100644 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/include.yml +++ b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/include.yml @@ -1,3 +1,10 @@ +# invalid include:rules +include: + - local: builds.yml + rules: + - if: '$INCLUDE_BUILDS == "true"' + when: on_success + # invalid trigger:include trigger missing file property: stage: prepare diff --git a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/include.yml b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/include.yml index c00ab0d464a76c..fffdda8e6d6b31 100644 --- a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/include.yml +++ b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/include.yml @@ -5,8 +5,8 @@ stages: include: - local: builds.yml rules: - - if: '$INCLUDE_BUILDS == "true"' - when: always + - if: '$INCLUDE_BUILDS == "false"' + when: never # valid trigger:include trigger:include accepts project and file properties: diff --git a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb index 6116fbced2bd67..10c1d92e209bbb 100644 --- a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb @@ -3,7 +3,7 @@ require 'fast_spec_helper' require_dependency 'active_model' -RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule do +RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule, feature_category: :pipeline_composition do let(:factory) do Gitlab::Config::Entry::Factory.new(described_class) .value(config) @@ -24,6 +24,12 @@ let(:config) { { if: '$THIS || $THAT' } } it { is_expected.to be_valid } + + context 'with when:' do + let(:config) { { if: '$THIS || $THAT', when: 'never' } } + + it { is_expected.to be_valid } + end end context 'when specifying an exists: clause' do @@ -90,6 +96,14 @@ it 'returns the config' do expect(subject).to eq(if: '$THIS || $THAT') end + + context 'with when:' do + let(:config) { { if: '$THIS || $THAT', when: 'never' } } + + it 'returns the config' do + expect(subject).to eq(if: '$THIS || $THAT', when: 'never') + end + end end context 'when specifying an exists: clause' do diff --git a/spec/lib/gitlab/ci/config/external/mapper/filter_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/filter_spec.rb index 5195567ebb4b0c..4da3e7e51a7b32 100644 --- a/spec/lib/gitlab/ci/config/external/mapper/filter_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper/filter_spec.rb @@ -18,6 +18,7 @@ describe '#process' do let(:locations) do [{ local: 'config/.gitlab-ci.yml', rules: [{ if: '$VARIABLE1' }] }, + { remote: 'https://testing.com/.gitlab-ci.yml', rules: [{ if: '$VARIABLE1', when: 'never' }] }, { remote: 'https://example.com/.gitlab-ci.yml', rules: [{ if: '$VARIABLE2' }] }] end @@ -28,5 +29,18 @@ [{ local: 'config/.gitlab-ci.yml', rules: [{ if: '$VARIABLE1' }] }] ) end + + context 'when FF `ci_support_include_rules_when_never` is disabled' do + before do + stub_feature_flags(ci_support_include_rules_when_never: false) + end + + it 'filters locations according to rules ignoring when:' do + is_expected.to eq( + [{ local: 'config/.gitlab-ci.yml', rules: [{ if: '$VARIABLE1' }] }, + { remote: 'https://testing.com/.gitlab-ci.yml', rules: [{ if: '$VARIABLE1', when: 'never' }] }] + ) + end + end end end diff --git a/spec/lib/gitlab/ci/config/external/rules_spec.rb b/spec/lib/gitlab/ci/config/external/rules_spec.rb index cc73338b5a81fe..c556d7d053b949 100644 --- a/spec/lib/gitlab/ci/config/external/rules_spec.rb +++ b/spec/lib/gitlab/ci/config/external/rules_spec.rb @@ -3,21 +3,27 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Config::External::Rules, feature_category: :pipeline_composition do - let(:rule_hashes) {} + let(:rule_hashes) { [{ if: '$MY_VAR == "hello"' }] } subject(:rules) { described_class.new(rule_hashes) } describe '#evaluate' do - let(:context) { double(variables_hash: {}) } + # Remove `project` property when FF `ci_support_include_rules_when_never` is removed + let(:context) { double(variables_hash: {}, project: nil) } subject(:result) { rules.evaluate(context).pass? } context 'when there is no rule' do + let(:rule_hashes) {} + it { is_expected.to eq(true) } end - context 'when there is a rule with if' do - let(:rule_hashes) { [{ if: '$MY_VAR == "hello"' }] } + shared_examples 'when there is a rule with if' do + # Remove this `before` block when FF `ci_support_include_rules_when_never` is removed + before do + allow(context).to receive(:project).and_return(nil) + end context 'when the rule matches' do let(:context) { double(variables_hash: { 'MY_VAR' => 'hello' }) } @@ -32,6 +38,8 @@ end end + it_behaves_like 'when there is a rule with if' + context 'when there is a rule with exists' do let(:project) { create(:project, :repository) } let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['test.md']) } @@ -53,11 +61,58 @@ end context 'when there is a rule with if and when' do - let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'on_success' }] } + context 'with when: never' do + # Remove this `before` block when FF `ci_support_include_rules_when_never` is removed + before do + allow(context).to receive(:project).and_return(nil) + end - it 'raises an error' do - expect { result }.to raise_error(described_class::InvalidIncludeRulesError, - 'invalid include rule: {:if=>"$MY_VAR == \"hello\"", :when=>"on_success"}') + let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'never' }] } + + context 'when the rule matches' do + let(:context) { double(variables_hash: { 'MY_VAR' => 'hello' }) } + + it { is_expected.to eq(false) } + end + + context 'when the rule does not match' do + let(:context) { double(variables_hash: { 'MY_VAR' => 'invalid' }) } + + it { is_expected.to eq(false) } + end + + context 'when FF `ci_support_include_rules_when_never` is disabled' do + before do + stub_feature_flags(ci_support_include_rules_when_never: false) + end + + context 'when the rule matches' do + let(:context) { double(variables_hash: { 'MY_VAR' => 'hello' }) } + + it { is_expected.to eq(true) } + end + + context 'when the rule does not match' do + let(:context) { double(variables_hash: { 'MY_VAR' => 'invalid' }) } + + it { is_expected.to eq(false) } + end + end + end + + context 'with when: ' do + let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'on_success' }] } + + it 'raises an error' do + expect { result }.to raise_error(described_class::InvalidIncludeRulesError, + 'invalid include rule: {:if=>"$MY_VAR == \"hello\"", :when=>"on_success"}') + end + end + + context 'with when: null' do + let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: nil }] } + + it_behaves_like 'when there is a rule with if' end end -- GitLab From 1efaf1d93dedfbec94e464bbc246b61551803e5a Mon Sep 17 00:00:00 2001 From: lma-git Date: Wed, 7 Jun 2023 10:34:42 -0700 Subject: [PATCH 2/4] Update documentation Updated the docs according to TW suggestions. --- doc/ci/yaml/includes.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/doc/ci/yaml/includes.md b/doc/ci/yaml/includes.md index 37dbafb5684115..7800da0fccd898 100644 --- a/doc/ci/yaml/includes.md +++ b/doc/ci/yaml/includes.md @@ -418,11 +418,17 @@ these keywords: ### `include` with `rules:if` +> Support for `when: never` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348146) in GitLab 16.1. + Use [`rules:if`](index.md#rulesif) to conditionally include other configuration files based on the status of CI/CD variables. For example: ```yaml include: + - local: builds.yml + rules: + - if: $NO_INCLUDES == "true" + when: never - local: builds.yml rules: - if: $INCLUDE_BUILDS == "true" @@ -435,23 +441,20 @@ test: script: exit 0 ``` -Use `when: never` to exclude the configuration file if the condition matches. For example: - -```yaml -include: - - local: builds.yml - rules: - - if: $INCLUDE_BUILDS == "false" - when: never -``` - ### `include` with `rules:exists` +> Support for `when: never` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348146) in GitLab 16.1. + Use [`rules:exists`](index.md#rulesexists) to conditionally include other configuration files based on the existence of files. For example: ```yaml include: + - local: builds.yml + rules: + - exists: + - exception-file.md + when: never - local: builds.yml rules: - exists: -- GitLab From f913770b43abe764f957111b4a7d87baa2b9f0df Mon Sep 17 00:00:00 2001 From: lma-git Date: Wed, 7 Jun 2023 14:21:24 -0700 Subject: [PATCH 3/4] Refactor specs Refactor specs to include tests for when there is a rule with exists and when. --- .../gitlab/ci/config/external/rules_spec.rb | 95 +++++++++++-------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/spec/lib/gitlab/ci/config/external/rules_spec.rb b/spec/lib/gitlab/ci/config/external/rules_spec.rb index c556d7d053b949..767d794360098b 100644 --- a/spec/lib/gitlab/ci/config/external/rules_spec.rb +++ b/spec/lib/gitlab/ci/config/external/rules_spec.rb @@ -3,14 +3,13 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Config::External::Rules, feature_category: :pipeline_composition do + # Remove `project` property when FF `ci_support_include_rules_when_never` is removed + let(:context) { double(variables_hash: {}, project: nil) } let(:rule_hashes) { [{ if: '$MY_VAR == "hello"' }] } subject(:rules) { described_class.new(rule_hashes) } describe '#evaluate' do - # Remove `project` property when FF `ci_support_include_rules_when_never` is removed - let(:context) { double(variables_hash: {}, project: nil) } - subject(:result) { rules.evaluate(context).pass? } context 'when there is no rule' do @@ -19,7 +18,7 @@ it { is_expected.to eq(true) } end - shared_examples 'when there is a rule with if' do + shared_examples 'when there is a rule with if' do |rule_matched_result = true, rule_not_matched_result = false| # Remove this `before` block when FF `ci_support_include_rules_when_never` is removed before do allow(context).to receive(:project).and_return(nil) @@ -28,26 +27,18 @@ context 'when the rule matches' do let(:context) { double(variables_hash: { 'MY_VAR' => 'hello' }) } - it { is_expected.to eq(true) } + it { is_expected.to eq(rule_matched_result) } end context 'when the rule does not match' do let(:context) { double(variables_hash: { 'MY_VAR' => 'invalid' }) } - it { is_expected.to eq(false) } + it { is_expected.to eq(rule_not_matched_result) } end end - it_behaves_like 'when there is a rule with if' - - context 'when there is a rule with exists' do + shared_examples 'when there is a rule with exists' do |file_exists_result = true, file_not_exists_result = false| let(:project) { create(:project, :repository) } - let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['test.md']) } - let(:rule_hashes) { [{ exists: 'Dockerfile' }] } - - context 'when the file does not exist' do - it { is_expected.to eq(false) } - end context 'when the file exists' do let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['Dockerfile']) } @@ -56,63 +47,83 @@ project.repository.create_file(project.first_owner, 'Dockerfile', "commit", message: 'test', branch_name: "master") end - it { is_expected.to eq(true) } + it { is_expected.to eq(file_exists_result) } + end + + context 'when the file does not exist' do + let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['test.md']) } + + it { is_expected.to eq(file_not_exists_result) } end end + it_behaves_like 'when there is a rule with if' + + context 'when there is a rule with exists' do + let(:rule_hashes) { [{ exists: 'Dockerfile' }] } + + it_behaves_like 'when there is a rule with exists' + end + context 'when there is a rule with if and when' do context 'with when: never' do - # Remove this `before` block when FF `ci_support_include_rules_when_never` is removed - before do - allow(context).to receive(:project).and_return(nil) - end - let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'never' }] } - context 'when the rule matches' do - let(:context) { double(variables_hash: { 'MY_VAR' => 'hello' }) } + it_behaves_like 'when there is a rule with if', false, false + + context 'when FF `ci_support_include_rules_when_never` is disabled' do + before do + stub_feature_flags(ci_support_include_rules_when_never: false) + end - it { is_expected.to eq(false) } + it_behaves_like 'when there is a rule with if' end + end - context 'when the rule does not match' do - let(:context) { double(variables_hash: { 'MY_VAR' => 'invalid' }) } + context 'with when: ' do + let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'on_success' }] } - it { is_expected.to eq(false) } + it 'raises an error' do + expect { result }.to raise_error(described_class::InvalidIncludeRulesError, + 'invalid include rule: {:if=>"$MY_VAR == \"hello\"", :when=>"on_success"}') end + end - context 'when FF `ci_support_include_rules_when_never` is disabled' do - before do - stub_feature_flags(ci_support_include_rules_when_never: false) - end + context 'with when: null' do + let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: nil }] } - context 'when the rule matches' do - let(:context) { double(variables_hash: { 'MY_VAR' => 'hello' }) } + it_behaves_like 'when there is a rule with if' + end + end - it { is_expected.to eq(true) } - end + context 'when there is a rule with exists and when' do + context 'with when: never' do + let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'never' }] } - context 'when the rule does not match' do - let(:context) { double(variables_hash: { 'MY_VAR' => 'invalid' }) } + it_behaves_like 'when there is a rule with exists', false, false - it { is_expected.to eq(false) } + context 'when FF `ci_support_include_rules_when_never` is disabled' do + before do + stub_feature_flags(ci_support_include_rules_when_never: false) end + + it_behaves_like 'when there is a rule with exists' end end context 'with when: ' do - let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'on_success' }] } + let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'on_success' }] } it 'raises an error' do expect { result }.to raise_error(described_class::InvalidIncludeRulesError, - 'invalid include rule: {:if=>"$MY_VAR == \"hello\"", :when=>"on_success"}') + 'invalid include rule: {:exists=>"Dockerfile", :when=>"on_success"}') end end context 'with when: null' do - let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: nil }] } + let(:rule_hashes) { [{ exists: 'Dockerfile', when: nil }] } - it_behaves_like 'when there is a rule with if' + it_behaves_like 'when there is a rule with exists' end end -- GitLab From c386c04b6da04d5ad4076e41368b05d73a94e1b1 Mon Sep 17 00:00:00 2001 From: lma-git Date: Tue, 13 Jun 2023 11:00:40 -0700 Subject: [PATCH 4/4] Update docs/comments --- doc/ci/yaml/includes.md | 2 +- lib/gitlab/ci/config/entry/include/rules/rule.rb | 3 +++ spec/lib/gitlab/ci/config/external/rules_spec.rb | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/ci/yaml/includes.md b/doc/ci/yaml/includes.md index 7800da0fccd898..84850ab7abb3a7 100644 --- a/doc/ci/yaml/includes.md +++ b/doc/ci/yaml/includes.md @@ -427,7 +427,7 @@ based on the status of CI/CD variables. For example: include: - local: builds.yml rules: - - if: $NO_INCLUDES == "true" + - if: $DONT_INCLUDE_BUILDS == "true" when: never - local: builds.yml rules: diff --git a/lib/gitlab/ci/config/entry/include/rules/rule.rb b/lib/gitlab/ci/config/entry/include/rules/rule.rb index 28e76454647b5a..60ce43a1546e19 100644 --- a/lib/gitlab/ci/config/entry/include/rules/rule.rb +++ b/lib/gitlab/ci/config/entry/include/rules/rule.rb @@ -14,6 +14,9 @@ class Rules::Rule < ::Gitlab::Config::Entry::Node attributes :if, :exists, :when + # Include rules are validated before Entry validations. This is because + # the include files are expanded before `compose!` runs in Ci::Config. + # The actual validation logic is in lib/gitlab/ci/config/external/rules.rb. validations do validates :config, presence: true validates :config, type: { with: Hash } diff --git a/spec/lib/gitlab/ci/config/external/rules_spec.rb b/spec/lib/gitlab/ci/config/external/rules_spec.rb index 767d794360098b..3cb9dedbefe6d3 100644 --- a/spec/lib/gitlab/ci/config/external/rules_spec.rb +++ b/spec/lib/gitlab/ci/config/external/rules_spec.rb @@ -80,7 +80,7 @@ end end - context 'with when: ' do + context 'with when: ' do let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'on_success' }] } it 'raises an error' do @@ -111,7 +111,7 @@ end end - context 'with when: ' do + context 'with when: ' do let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'on_success' }] } it 'raises an error' do -- GitLab