Implement allowed-skips input

This change lets the end-users mark certain jobs as allowed to be non-
voting when skipped only but their failures will affect the outcome.

Resolves #1
This commit is contained in:
Sviatoslav Sydorenko
2021-12-14 04:02:06 +01:00
parent 290bb0f92a
commit 3a2de129f0
3 changed files with 59 additions and 11 deletions
+8 -4
View File
@@ -78,6 +78,7 @@ jobs:
uses: re-actors/alls-green@release/v1
with:
allowed-failures: docs, linters
allowed-skips: non-voting-flaky-job
jobs: ${{ toJSON(needs) }}
...
```
@@ -85,10 +86,13 @@ jobs:
## Options
There are two options — `allowed-failures` and `jobs`. The former is
optional but the later is mandatory. `allowed-failures` tells the action
which jobs should not affect the outcome, by default all the jobs will
be "voting". `jobs` is an object representing the jobs that should
There are three options — `allowed-failures`, `allowed-skips` and
`jobs`. The first two are optional but `jobs` is mandatory.
`allowed-failures` tells the action which jobs should not affect the
outcome if they don't succeed, by default all the jobs will
be "voting". Same goes for `allowed-skips` — it won't allow the listed
jobs to affect the outcome if they are skipped but are still "voting" in
case they run. `jobs` is an object representing the jobs that should
affect the decision of whether the pipeline failed or not, it is
important to pass a JSON-serialized `needs` context to this argument.
+11
View File
@@ -23,6 +23,13 @@ inputs:
Job names that are allowed to fail and not affect the outcome,
as a comma-separated list or serialized as a JSON string
required: false
allowed-skips:
default: >-
[]
description: >-
Job names that are allowed to be skipped and not affect the
outcome, as a comma-separated list or serialized as a JSON string
required: false
outputs:
failure:
@@ -48,6 +55,10 @@ runs:
${{ inputs.allowed-failures }}
EOM
)" \
"$(cat << EOM
${{ inputs.allowed-skips }}
EOM
)" \
"$(cat << EOM
${{ inputs.jobs }}
EOM
+40 -7
View File
@@ -35,13 +35,14 @@ def parse_as_list(input_text):
return [s.strip() for s in input_text.split(',')]
def parse_inputs(raw_allowed_failures, raw_jobs):
def parse_inputs(raw_allowed_failures, raw_allowed_skips, raw_jobs):
"""Normalize the action inputs by turning them into data."""
allowed_failures_input = parse_as_list(raw_allowed_failures)
allowed_skips_input = parse_as_list(raw_allowed_skips)
return {
'allowed_failures': allowed_failures_input,
'allowed_skips': allowed_skips_input,
'jobs': json.loads(raw_jobs),
}
@@ -49,7 +50,9 @@ def parse_inputs(raw_allowed_failures, raw_jobs):
def log_decision_details(
job_matrix_succeeded,
jobs_allowed_to_fail,
jobs_allowed_to_be_skipped,
allowed_to_fail_jobs_succeeded,
allowed_to_be_skipped_jobs_succeeded,
jobs,
):
"""Record the decisions made into console output."""
@@ -73,27 +76,46 @@ def log_decision_details(
)
if jobs_allowed_to_be_skipped and allowed_to_be_skipped_jobs_succeeded:
print_to_stderr(
'🛈 All of the allowed to be skipped dependency jobs succeeded.',
)
elif jobs_allowed_to_fail:
print_to_stderr(
'🛈 Some of the allowed to be skipped jobs did not succeed.',
)
print_to_stderr('📝 Job statuses:')
for name, job in jobs.items():
print_to_stderr(
'📝 {name} → {emoji} {result} [{status}]'.
format(
emoji='✓' if job['result'] == 'success' else '❌',
emoji='✓' if job['result'] == 'success'
else '❌' if job['result'] == 'failure'
else '⬜',
name=name,
result=job['result'],
status='allowed to fail' if name in jobs_allowed_to_fail
else 'required to succeed',
else 'required to succeed'
if name not in jobs_allowed_to_be_skipped
else 'required to succeed or be skipped',
),
)
def main(argv):
"""Decide whether the needed jobs got satisfactory results."""
inputs = parse_inputs(raw_allowed_failures=argv[1], raw_jobs=argv[2])
inputs = parse_inputs(
raw_allowed_failures=argv[1],
raw_allowed_skips=argv[2],
raw_jobs=argv[3],
)
jobs = inputs['jobs'] or {}
jobs_allowed_to_fail = inputs['allowed_failures'] or []
jobs_allowed_to_fail = set(inputs['allowed_failures'] or [])
jobs_allowed_to_be_skipped = set(inputs['allowed_skips'] or [])
if not jobs:
sys.exit(
@@ -104,7 +126,10 @@ def main(argv):
job_matrix_succeeded = all(
job['result'] == 'success' for name, job in jobs.items()
if name not in jobs_allowed_to_fail
if name not in (jobs_allowed_to_fail | jobs_allowed_to_be_skipped)
) and all(
job['result'] in {'skipped', 'success'} for name, job in jobs.items()
if name in jobs_allowed_to_be_skipped
)
set_final_result_outputs(job_matrix_succeeded)
@@ -115,10 +140,18 @@ def main(argv):
)
allowed_to_be_skipped_jobs_succeeded = all(
job['result'] == 'success' for name, job in jobs.items()
if name in jobs_allowed_to_be_skipped
)
log_decision_details(
job_matrix_succeeded,
jobs_allowed_to_fail,
jobs_allowed_to_be_skipped,
allowed_to_fail_jobs_succeeded,
allowed_to_be_skipped_jobs_succeeded,
jobs,
)