Skip to content
Snippets Groups Projects
Verified Commit 68b7600b authored by David Dieulivol's avatar David Dieulivol :speech_balloon: Committed by GitLab
Browse files

Merge branch 'jennli-remove-ci-validation-danger' into 'master'

Remove CI dependency validation danger

See merge request gitlab-org/gitlab!161222



Merged-by: default avatarDavid Dieulivol <ddieulivol@gitlab.com>
Approved-by: default avatarDavid Dieulivol <ddieulivol@gitlab.com>
Co-authored-by: default avatarJennifer Li <jli@gitlab.com>
parents a1327986 882ad749
No related branches found
No related tags found
2 merge requests!164749Enable parallel in test-on-omnibus,!161222Remove CI dependency validation danger
Pipeline #1411306267 passed
...@@ -17,9 +17,4 @@ Please consider the effect of the changes in this merge request on the following ...@@ -17,9 +17,4 @@ Please consider the effect of the changes in this merge request on the following
Please consider communicating these changes to the broader team following the [communication guideline for pipeline changes](https://about.gitlab.com/handbook/engineering/quality/engineering-productivity/#pipeline-changes) Please consider communicating these changes to the broader team following the [communication guideline for pipeline changes](https://about.gitlab.com/handbook/engineering/quality/engineering-productivity/#pipeline-changes)
MSG MSG
if helper.has_ci_changes? markdown(PIPELINE_CHANGES_MESSAGE) if helper.has_ci_changes?
markdown(PIPELINE_CHANGES_MESSAGE)
dependency_validation_message = ci_jobs_dependency_validation.output_message
markdown(dependency_validation_message) unless dependency_validation_message.empty?
end
# frozen_string_literal: true
require_relative '../../tooling/danger/ci_jobs_dependency_validation'
module Danger
class CiJobsDependencyValidation < ::Danger::Plugin
include Tooling::Danger::CiJobsDependencyValidation
end
end
# frozen_string_literal: true
require 'gitlab/dangerfiles/spec_helper'
require 'fast_spec_helper'
require 'webmock/rspec'
require_relative '../../../tooling/danger/ci_jobs_dependency_validation'
RSpec.describe Tooling::Danger::CiJobsDependencyValidation, feature_category: :tooling do
include_context 'with dangerfile'
let(:ci) { true }
let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) }
let(:rules_base) do
[
{
'if' => '$CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "detached"',
'changes' => ["doc/index.md"]
}
]
end
let(:job_config_base) { { 'rules' => rules_base, 'needs' => [] } }
let(:new_condition) { { 'if' => '$NEW_VAR == "true"' } }
let(:rules_with_new_condition) { [*rules_base, new_condition] }
let(:validated_jobs_base) do
described_class::VALIDATED_JOB_NAMES.index_with { job_config_base }
end
let(:master_merged_yaml) do
YAML.dump({
'job1' => job_config_base
})
end
let(:query) do
{
content_ref: 'merged_result_commit_sha',
dry_run_ref: 'feature_branch',
include_jobs: true,
dry_run: true
}
end
subject(:ci_jobs_dependency_validation) { fake_danger.new(helper: fake_helper) }
before do
stub_env('CI_COMMIT_SHA', 'merged_result_commit_sha')
allow(ci_jobs_dependency_validation).to receive_message_chain(:gitlab, :api, :get).with("/projects/1/ci/lint",
query: {}) do
{ 'merged_yaml' => master_merged_yaml }
end
allow(ci_jobs_dependency_validation).to receive_message_chain(:gitlab, :api, :get).with("/projects/1/ci/lint",
query: query) do
{ 'merged_yaml' => source_branch_merged_yaml }
end
allow(ci_jobs_dependency_validation.helper).to receive(:ci?).and_return(ci)
allow(ci_jobs_dependency_validation.helper).to receive(:has_ci_changes?).and_return(true)
allow(ci_jobs_dependency_validation.helper).to receive(:mr_source_branch).and_return('feature_branch')
allow(ci_jobs_dependency_validation.helper).to receive(:mr_target_branch).and_return('master')
allow(ci_jobs_dependency_validation.helper).to receive(:mr_source_project_id).and_return('1')
allow(ci_jobs_dependency_validation.helper).to receive(:mr_target_project_id).and_return('1')
allow($stdout).to receive(:puts)
end
describe '#output_message' do
shared_examples 'output message' do |warning|
it 'outputs messages' do
if warning
expect(ci_jobs_dependency_validation).to receive(:warn).with(described_class::FAILED_VALIDATION_WARNING)
else
expect(ci_jobs_dependency_validation).not_to receive(:warn)
end
expect(ci_jobs_dependency_validation.output_message).to eq(expected_message)
end
end
context 'when not in ci environment' do
let(:ci) { false }
let(:expected_message) { '' }
it_behaves_like 'output message'
end
context 'when in ci environment' do
context 'with no ci changes' do
let(:expected_message) { '' }
before do
allow(ci_jobs_dependency_validation.helper).to receive(:has_ci_changes?).and_return(false)
end
it_behaves_like 'output message'
end
context 'with api fails to retrieve jobs from target branch' do
let(:error_msg) { '404 not found' }
before do
allow(
ci_jobs_dependency_validation
).to receive_message_chain(:gitlab, :api, :get).with("/projects/1/ci/lint", query: {}).and_raise(error_msg)
end
it 'warns validation is skipped and outputs empty message' do
expect(ci_jobs_dependency_validation).to receive(:warn).with(
"#{described_class::SKIPPED_VALIDATION_WARNING}: #{error_msg}"
)
expect { expect(ci_jobs_dependency_validation.output_message).to eq('') }.tap do |expectation|
expectation
.to output(<<~MSG).to_stdout
Initializing 0 jobs from master ci config...
MSG
end
end
end
context 'with api fails to retrieve jobs from source branch' do
let(:error_msg) { '404 not found' }
before do
allow(
ci_jobs_dependency_validation
).to receive_message_chain(:gitlab, :api, :get).with("/projects/1/ci/lint", query: query).and_raise(error_msg)
end
it 'warns validation is skipped and outputs empty message' do
expect(ci_jobs_dependency_validation).to receive(:warn).with(
"#{described_class::SKIPPED_VALIDATION_WARNING}: #{error_msg}"
)
expect { expect(ci_jobs_dependency_validation.output_message).to eq('') }.tap do |expectation|
expectation
.to output(<<~MSG).to_stdout
Initializing 1 jobs from master ci config...
Initializing 0 jobs from feature_branch ci config...
MSG
end
end
end
context 'with api returns nil for merged yaml' do
let(:source_branch_merged_yaml) { nil }
it 'warns validation is skipped and outputs empty message' do
expect(ci_jobs_dependency_validation).to receive(:warn).with(
"#{described_class::SKIPPED_VALIDATION_WARNING}: no implicit conversion of nil into String"
)
expect(ci_jobs_dependency_validation.output_message).to eq('')
end
end
context 'when target branch jobs is empty' do
let(:source_branch_merged_yaml) { YAML.dump({}) }
let(:expected_message) { '' }
it_behaves_like 'output message'
end
context 'when source branch jobs is empty' do
let(:master_merged_yaml) { YAML.dump({}) }
let(:expected_message) { '' }
it_behaves_like 'output message'
end
context 'when jobs do not have dependencies' do
let(:source_branch_merged_yaml) { YAML.dump(validated_jobs_base) }
let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
it_behaves_like 'output message'
end
context 'when needed jobs are missing is source branch' do
let(:source_branch_merged_yaml) do
YAML.dump({ 'job1' => job_config_base.merge({ 'rules' => rules_with_new_condition }) })
end
let(:expected_message) do
warning_details = described_class::VALIDATED_JOB_NAMES.map do |job_name|
<<~MARKDOWN
- :warning: Unable to find job `#{job_name}` in branch `feature_branch`.
If this job has been removed, please delete it from `Tooling::Danger::CiJobsDependencyValidation::VALIDATED_JOB_NAMES`.
Validation skipped.
MARKDOWN
end.join("\n")
<<~MARKDOWN.chomp
### CI Jobs Dependency Validation
| name | validation status |
| ------ | --------------- |
| `setup-test-env` | :warning: Skipped |
| `compile-test-assets` | :warning: Skipped |
| `retrieve-tests-metadata` | :warning: Skipped |
| `build-gdk-image` | :warning: Skipped |
| `build-assets-image` | :warning: Skipped |
| `build-qa-image` | :warning: Skipped |
| `e2e-test-pipeline-generate` | :warning: Skipped |
#{warning_details}
MARKDOWN
end
it_behaves_like 'output message', true
end
context 'when job1 in branch needs one other job to run' do
let(:job_name) { 'job1' }
let(:needed_job_name) { 'setup-test-env' }
let(:source_branch_merged_yaml) do
YAML.dump(validated_jobs_base.merge(
{
job_name => {
'rules' => rules_with_new_condition,
'needs' => [needed_job_name]
}
}
))
end
context 'with a hidden job' do
let(:job_name) { '.job1' }
let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
it_behaves_like 'output message'
end
context 'with a global keyword' do
let(:job_name) { 'default' }
let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
it_behaves_like 'output message'
end
context 'when the dependent job config has not changed (identical in master and in branch)' do
let(:master_merged_yaml) { source_branch_merged_yaml }
let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
it_behaves_like 'output message'
end
context 'when VALIDATED_JOB_NAMES does not contain the needed job' do
let(:needed_job_name) { 'not-recognized' }
let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
it_behaves_like 'output message'
end
context 'when VALIDATED_JOB_NAMES contains the needed job and dependent job config changed' do
context 'when the added rule is also present in its needed job' do
let(:source_branch_merged_yaml) do
YAML.dump(validated_jobs_base.merge({
job_name => job_config_base.merge({
'rules' => rules_with_new_condition,
'needs' => [needed_job_name]
}),
needed_job_name => { 'rules' => rules_with_new_condition, 'needs' => [] }
}))
end
let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
it_behaves_like 'output message'
end
context 'when an added rule is missing in its needed job' do
let(:expected_message) do
<<~MARKDOWN
### CI Jobs Dependency Validation
| name | validation status |
| ------ | --------------- |
| `setup-test-env` | 🚨 Failed (1) |
| `compile-test-assets` | :white_check_mark: Passed |
| `retrieve-tests-metadata` | :white_check_mark: Passed |
| `build-gdk-image` | :white_check_mark: Passed |
| `build-assets-image` | :white_check_mark: Passed |
| `build-qa-image` | :white_check_mark: Passed |
| `e2e-test-pipeline-generate` | :white_check_mark: Passed |
- 🚨 **These rule changes do not match with rules for `setup-test-env`:**
<details><summary>Click to expand details</summary>
`job1`:
- Added rules:
```yaml
- if: $NEW_VAR == "true"
```
- Removed rules:
`N/A`
Here are the rules for `setup-test-env`:
```yaml
- if: $CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE
== "detached"
changes:
- doc/index.md
```
</details>
To avoid CI config errors, please verify if the same rule addition/removal should be applied to `setup-test-env`.
If not, please add a comment to explain why.
MARKDOWN
end
it_behaves_like 'output message', true
end
end
end
context 'when job configs are malformatted' do
let(:source_branch_merged_yaml) do
YAML.dump(validated_jobs_base.merge(
{
'job1' => 'not a hash',
'job2' => ['array'],
'job3' => { 'key' => 'missing needs and rules' }
}
))
end
let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
it_behaves_like 'output message'
end
context 'when dependent job has a rule that is not a hash' do
let(:source_branch_merged_yaml) do
YAML.dump(
validated_jobs_base.merge({
'job1' => {
'rules' => ['this is a malformatted rule'],
'needs' => 'this is a malformatted needs'
}
})
)
end
let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
it_behaves_like 'output message'
end
context 'when dependent job have an added rule but condition reads "when: never"' do
let(:new_condition) { { 'if' => "$NEW_VAR == true", 'when' => 'never' } }
let(:source_branch_merged_yaml) do
YAML.dump(
validated_jobs_base.merge({
'job1' => {
'rules' => [new_condition],
'needs' => ['setup-test-env']
}
})
)
end
let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
it_behaves_like 'output message'
end
context 'when dependent job has removed a rule with "when: never"' do
let(:needed_job_rules) { rules_base }
let(:master_merged_yaml) do
YAML.dump(
validated_jobs_base.merge({
'job1' => {
'rules' => [*rules_base, { 'if' => 'true', 'when' => 'never' }],
'needs' => ['setup-test-env']
}
})
)
end
let(:source_branch_merged_yaml) do
YAML.dump(
validated_jobs_base.merge({
'job1' => {
'rules' => rules_base,
'needs' => ['setup-test-env']
},
'setup-test-env' => {
'rules' => needed_job_rules
}
})
)
end
context 'when needed_job does not have this negative rule' do
let(:expected_message) { ':white_check_mark: No warnings found in ci job dependencies.' }
it_behaves_like 'output message'
end
context 'when needed_job still has this negative rule' do
let(:needed_job_rules) { [*rules_base, { 'if' => 'true', 'when' => 'never' }] }
let(:expected_message) do
<<~MARKDOWN
### CI Jobs Dependency Validation
| name | validation status |
| ------ | --------------- |
| `setup-test-env` | 🚨 Failed (1) |
| `compile-test-assets` | :white_check_mark: Passed |
| `retrieve-tests-metadata` | :white_check_mark: Passed |
| `build-gdk-image` | :white_check_mark: Passed |
| `build-assets-image` | :white_check_mark: Passed |
| `build-qa-image` | :white_check_mark: Passed |
| `e2e-test-pipeline-generate` | :white_check_mark: Passed |
- 🚨 **These rule changes do not match with rules for `setup-test-env`:**
<details><summary>Click to expand details</summary>
`job1`:
- Added rules:
`N/A`
- Removed rules:
```yaml
- if: 'true'
when: never
```
Here are the rules for `setup-test-env`:
```yaml
- if: $CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE
== "detached"
changes:
- doc/index.md
- if: 'true'
when: never
```
</details>
To avoid CI config errors, please verify if the same rule addition/removal should be applied to `setup-test-env`.
If not, please add a comment to explain why.
MARKDOWN
end
it_behaves_like 'output message', true
end
end
context 'when dependent job have modified rules, but its attributes have nested arrays' do
let(:source_branch_merged_yaml) do
YAML.dump(
validated_jobs_base.merge({
'job1' => {
'rules' => [{ 'if' => 'true', 'when' => 'always' }, [new_condition]],
'needs' => ['setup-test-env', %w[compile-test-assets retrieve-tests-metadata]]
}
})
)
end
let(:message_preview) do
<<~MARKDOWN
### CI Jobs Dependency Validation
| name | validation status |
| ------ | --------------- |
| `setup-test-env` | 🚨 Failed (1) |
| `compile-test-assets` | 🚨 Failed (1) |
| `retrieve-tests-metadata` | 🚨 Failed (1) |
| `build-gdk-image` | :white_check_mark: Passed |
| `build-assets-image` | :white_check_mark: Passed |
| `build-qa-image` | :white_check_mark: Passed |
| `e2e-test-pipeline-generate` | :white_check_mark: Passed |
MARKDOWN
end
let(:expected_message) do
%w[setup-test-env compile-test-assets retrieve-tests-metadata].map do |job_name|
<<~MARKDOWN
- 🚨 **These rule changes do not match with rules for `#{job_name}`:**
<details><summary>Click to expand details</summary>
`job1`:
- Added rules:
```yaml
- if: 'true'
when: always
- if: $NEW_VAR == "true"
```
- Removed rules:
`N/A`
Here are the rules for `#{job_name}`:
```yaml
- if: $CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE
== "detached"
changes:
- doc/index.md
```
</details>
To avoid CI config errors, please verify if the same rule addition/removal should be applied to `#{job_name}`.
If not, please add a comment to explain why.
MARKDOWN
end.join('').prepend(message_preview).chomp
end
it_behaves_like 'output message', true
end
end
end
describe '#fetch_jobs_yaml' do
context 'with api returns error' do
before do
allow(
ci_jobs_dependency_validation
).to receive_message_chain(:gitlab, :api, :get).with("/projects/1/ci/lint", query: {}).and_raise('error')
end
it 'returns empty object' do
expect(ci_jobs_dependency_validation).to receive(:warn).with(
"#{described_class::SKIPPED_VALIDATION_WARNING}: error"
)
expect(ci_jobs_dependency_validation.send(:fetch_jobs_yaml, '1', 'master')).to eq({})
end
end
context 'with returned payload missing merged_yaml' do
before do
allow(
ci_jobs_dependency_validation
).to receive_message_chain(:gitlab, :api, :get).with(
"/projects/1/ci/lint", query: {}
).and_return({ 'errors' => ['error'] })
end
it 'returns empty object' do
expect(ci_jobs_dependency_validation).to receive(:warn).with(
"#{described_class::SKIPPED_VALIDATION_WARNING}: error"
)
expect(ci_jobs_dependency_validation.send(:fetch_jobs_yaml, '1', 'master')).to eq({})
end
end
context 'with returned payload has merged_yaml and also has errors' do
before do
allow(
ci_jobs_dependency_validation
).to receive_message_chain(:gitlab, :api, :get).with(
"/projects/1/ci/lint", query: {}
).and_return({ 'errors' => ['error'], 'merged_yaml' => master_merged_yaml })
end
it 'returns the yaml and disregard the errors' do
expect(ci_jobs_dependency_validation.send(:fetch_jobs_yaml, '1', 'master')).to eq(
YAML.load(master_merged_yaml)
)
end
end
context 'with returned merged_yaml cannot be parsed' do
before do
allow(
ci_jobs_dependency_validation
).to receive_message_chain(:gitlab, :api, :get).with("/projects/1/ci/lint", query: {}).and_return(
{ 'merged_yaml' => 'date: 2024-04-04' }
)
end
it 'returns empty object' do
expect(ci_jobs_dependency_validation).to receive(:warn).with(
"#{described_class::SKIPPED_VALIDATION_WARNING}: Tried to load unspecified class: Date"
)
expect(ci_jobs_dependency_validation.send(:fetch_jobs_yaml, '1', 'master')).to eq({})
end
end
end
end
# frozen_string_literal: true
module Tooling
module Danger
module CiJobsDependencyValidation
VALIDATED_JOB_NAMES = %w[
setup-test-env
compile-test-assets
retrieve-tests-metadata
build-gdk-image
build-assets-image
build-qa-image
e2e-test-pipeline-generate
].freeze
GLOBAL_KEYWORDS = %w[workflow variables stages default].freeze
DEFAULT_BRANCH_NAME = 'master'
FAILED_VALIDATION_WARNING = 'Please review warnings in the *CI Jobs Dependency Validation* section below.'
SKIPPED_VALIDATION_WARNING = 'Job dependency validation is skipped due to error fetching merged CI yaml'
VALIDATION_PASSED_OUTPUT = ':white_check_mark: No warnings found in ci job dependencies.'
Job = Struct.new(:name, :rules, :needs, keyword_init: true) do
def self.parse_rules_from_yaml(name, jobs_yaml)
attribute_values(jobs_yaml, name, 'rules').filter_map do |rule|
rule.is_a?(Hash) ? rule.slice('if', 'changes', 'when') : rule
end
end
def self.parse_needs_from_yaml(name, jobs_yaml)
attribute_values(jobs_yaml, name, 'needs').map { |need| need.is_a?(Hash) ? need['job'] : need }
end
def self.attribute_values(jobs_yaml, name, attribute)
return [] if jobs_yaml.nil? || jobs_yaml.empty? || !jobs_yaml[name].is_a?(Hash)
values = jobs_yaml.dig(name, attribute)
values.nil? ? [] : Array(values).flatten
end
def self.ignore?(job_name)
GLOBAL_KEYWORDS.include?(job_name) || job_name.start_with?('.')
end
def dependent_jobs(jobs)
jobs.select do |job|
!Job.ignore?(job.name) && job.needs.include?(name)
end
end
end
def output_message
return '' if !helper.ci? || !helper.has_ci_changes? || target_branch_jobs.empty? || source_branch_jobs.empty?
validation_statuses = VALIDATED_JOB_NAMES.to_h do |job_name|
[job_name, { skipped: false, failed: 0 }]
end
output = VALIDATED_JOB_NAMES.filter_map do |needed_job_name|
validate(needed_job_name, validation_statuses)
end.join("\n").chomp
return VALIDATION_PASSED_OUTPUT if output == ''
warn FAILED_VALIDATION_WARNING
<<~MARKDOWN
### CI Jobs Dependency Validation
| name | validation status |
| ------ | --------------- |
#{construct_summary_table(validation_statuses)}
#{output}
MARKDOWN
end
private
def target_branch_jobs
@target_branch_jobs ||= build_jobs_from_yaml(target_branch_jobs_yaml, target_branch)
end
def source_branch_jobs
@source_branch_jobs ||= build_jobs_from_yaml(source_branch_jobs_yaml, source_branch)
end
def target_branch_jobs_yaml
@target_branch_jobs_yaml ||= fetch_jobs_yaml(target_project_id, target_branch)
end
def source_branch_jobs_yaml
@source_branch_jobs_yaml ||= fetch_jobs_yaml(source_project_id, source_branch)
end
def fetch_jobs_yaml(project_id, ref)
api_response = gitlab.api.get(lint_path(project_id), query: query_params(ref))
raise api_response['errors'].first if api_response['merged_yaml'].nil? && api_response['errors']&.any?
YAML.load(api_response['merged_yaml'], aliases: true)
rescue StandardError => e
warn "#{SKIPPED_VALIDATION_WARNING}: #{e.message}"
{}
end
def build_jobs_from_yaml(jobs_yaml, ref)
puts "Initializing #{jobs_yaml.keys.count} jobs from #{ref} ci config..."
jobs_yaml.filter_map do |job_name, _job_data|
next if Job.ignore?(job_name)
Job.new(
name: job_name,
rules: Job.parse_rules_from_yaml(job_name, jobs_yaml),
needs: Job.parse_needs_from_yaml(job_name, jobs_yaml)
)
end
end
def query_params(ref)
ref_query_params = {
content_ref: merged_result_commit_sha,
dry_run_ref: ref,
include_jobs: true,
dry_run: true
}
ref == DEFAULT_BRANCH_NAME ? {} : ref_query_params
end
def validate(needed_job_name, validation_statuses)
needed_job_in_source_branch = source_branch_jobs.find { |job| job.name == needed_job_name }
needed_job_in_target_branch = target_branch_jobs.find { |job| job.name == needed_job_name }
if needed_job_in_source_branch.nil?
validation_statuses[needed_job_name][:skipped] = true
return <<~MARKDOWN
- :warning: Unable to find job `#{needed_job_name}` in branch `#{source_branch}`.
If this job has been removed, please delete it from `Tooling::Danger::CiJobsDependencyValidation::VALIDATED_JOB_NAMES`.
Validation skipped.
MARKDOWN
end
failures = validation_failures(
needed_job_in_source_branch: needed_job_in_source_branch,
needed_job_in_target_branch: needed_job_in_target_branch
)
failed_count = failures.count
return if failed_count == 0
validation_statuses[needed_job_name][:failed] = failed_count
<<~MSG
- 🚨 **These rule changes do not match with rules for `#{needed_job_name}`:**
<details><summary>Click to expand details</summary>
#{failures.join("\n")}
Here are the rules for `#{needed_job_name}`:
```yaml
#{dump_yaml(needed_job_in_source_branch.rules)}
```
</details>
To avoid CI config errors, please verify if the same rule addition/removal should be applied to `#{needed_job_name}`.
If not, please add a comment to explain why.
MSG
end
def construct_summary_table(validation_statuses)
validation_statuses.map do |job_name, statuses|
skipped, failed_count = statuses.values_at(:skipped, :failed)
summary = if skipped
":warning: Skipped"
elsif failed_count == 0
":white_check_mark: Passed"
else
"🚨 Failed (#{failed_count})"
end
<<~MARKDOWN.chomp
| `#{job_name}` | #{summary} |
MARKDOWN
end.join("\n")
end
def validation_failures(needed_job_in_source_branch:, needed_job_in_target_branch:)
dependent_jobs_new = needed_job_in_source_branch&.dependent_jobs(source_branch_jobs) || []
dependent_jobs_old = needed_job_in_target_branch&.dependent_jobs(target_branch_jobs) || []
(dependent_jobs_new - dependent_jobs_old).filter_map do |dependent_job_with_rule_change|
dependent_job_old = dependent_jobs_old.find do |target_branch_job|
target_branch_job.name == dependent_job_with_rule_change.name
end
new_rules = dependent_job_with_rule_change.rules
old_rules = dependent_job_old&.rules
added_rules_to_report = rules_missing_in_needed_job(
needed_job: needed_job_in_source_branch,
rules: dependent_job_old.nil? ? new_rules : new_rules - old_rules # added rules
)
removed_rules_to_report = removed_negative_rules_present_in_needed_job(
needed_job: needed_job_in_source_branch,
rules: dependent_job_old.nil? ? [] : old_rules - new_rules # removed rules
)
next if added_rules_to_report.empty? && removed_rules_to_report.empty?
<<~MARKDOWN
`#{dependent_job_with_rule_change.name}`:
- Added rules:
#{report_yaml_markdown(added_rules_to_report)}
- Removed rules:
#{report_yaml_markdown(removed_rules_to_report)}
MARKDOWN
end
end
def report_yaml_markdown(rules_to_report)
return '`N/A`' unless rules_to_report.any?
<<~MARKDOWN.chomp
```yaml
#{dump_yaml(rules_to_report)}
```
MARKDOWN
end
def dump_yaml(yaml)
YAML.dump(yaml).delete_prefix("---\n").chomp
end
# Limitation: missing rules in needed jobs does not always mean the config is invalid.
# needed_jobs can have very generic rules, for example
# - rule-for-job1:
# - <<: *if-merge-request-targeting-stable-branch
# - rule-for-needed_job:
# - <<: *if-merge-request-targeting-all-branches
# The above config is still valid, but danger will still print a warning because the exact rule is missing.
# We will have to manually identify that this config is fine and the warning should be ignored.
def rules_missing_in_needed_job(rules:, needed_job:)
return [] if rules.empty?
rules.select do |rule|
!needed_job.rules.include?(rule) && !negative_rule?(rule)
end
end
def removed_negative_rules_present_in_needed_job(rules:, needed_job:)
return [] if rules.empty?
rules.select do |rule|
needed_job.rules.include?(rule) && negative_rule?(rule)
end
end
def negative_rule?(rule)
rule.is_a?(Hash) && rule['when'] == 'never'
end
def lint_path(project_id)
"/projects/#{project_id}/ci/lint"
end
def source_project_id
helper.mr_source_project_id
end
def target_project_id
helper.mr_target_project_id
end
def source_branch
helper.mr_source_branch
end
def merged_result_commit_sha
ENV['CI_COMMIT_SHA'] # so we validate the merged results commit
end
def target_branch
helper.mr_target_branch
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment