NAME

App::Actions::Runner::Report - what a run tells its user at the end

SYNOPSIS

my $report = App::Actions::Runner::Report->new(docker => $docker);

$report->record('test (perl=5.40)', 'success');
$report->record('lint', 'failure')->fail;
$report->print_summary;    # === Workflow summary === ... Workflow FAILED

Keep the logs of a parallel run, and say where they went:

$report->keep_logs($parallel->logs, App::Actions::Logs::default_directory(), $run_id)
	->print_kept_logs;

Tell the user what a run with keep left behind:

$report->print_kept_resources($containers->remaining, $temp_path);

Decide the exit status:

exit($report->failed ? 1 : 0);

DESCRIPTION

In short: this module writes what a run tells you at the end: one line per job, whether the workflow passed, and where the logs are. It is a part of App::Actions::Runner; you rarely use it on its own.

Collects one line per job as the run goes, says at the end whether the workflow succeeded, and keeps the logs of the jobs that ran in parallel: they are copied out of the temporary directory before it is removed, so a failed job's own account of what went wrong can be read after the run (and action-run logs lists them).

METHODS

new

ARGUMENTS

  • docker - used to mask secrets in job names.

  • quiet - optional; print nothing.

RETURNS

An App::Actions::Runner::Report object.

SIDE EFFECTS

None.

EXAMPLE

my $report = App::Actions::Runner::Report->new(docker => $docker, quiet => 0);

API SPECIFICATION

INPUT

{
	docker => { type => 'object' },
	quiet  => { type => 'boolean', optional => 1 },
}

OUTPUT

{ type => 'object', isa => 'App::Actions::Runner::Report' }

MESSAGES

+---------------------------+------------------------------+-----------------------------+
| Message                   | Meaning                      | Resolution                  |
+---------------------------+------------------------------+-----------------------------+
| Invalid arguments to      | docker is missing            | Pass the Docker object      |
| ...Report::new: REASON    |                              |                             |
+---------------------------+------------------------------+-----------------------------+

record

$report->record($display_name, $result);

Adds a job's line to the summary. Returns the object.

EXAMPLE

$report->record('lint', 'skipped');

API SPECIFICATION

INPUT

{
	name   => { type => 'string' },
	result => { type => 'string', memberof => [ 'success', 'failure', 'skipped' ] },
}

OUTPUT

{ type => 'object', isa => 'App::Actions::Runner::Report' }

MESSAGES

None.

fail

Marks the workflow as failed. Returns the object.

EXAMPLE

$report->record($name, 'failure')->fail unless $continue_on_error;

API SPECIFICATION

INPUT

{}

OUTPUT

{ type => 'object', isa => 'App::Actions::Runner::Report' }

MESSAGES

None.

failed

Says whether the workflow has failed: 1 or 0.

EXAMPLE

return $report->failed ? 1 : 0;

API SPECIFICATION

INPUT

{}

OUTPUT

{ type => 'boolean' }

MESSAGES

None.

Prints one line per job and whether the workflow succeeded, unless quiet. Returns the object.

EXAMPLE

$report->print_summary;
#
# === Workflow summary ===
#   test (perl=5.40)                                   success
# Workflow succeeded

API SPECIFICATION

INPUT

{}

OUTPUT

{ type => 'object', isa => 'App::Actions::Runner::Report' }

MESSAGES

+---------------------------+------------------------------+-----------------------------+
| Message                   | Meaning                      | Resolution                  |
+---------------------------+------------------------------+-----------------------------+
| No jobs were run.         | Every job was skipped or     | Check --job and the jobs'   |
|                           | none was selected            | conditions                  |
| Workflow FAILED           | A job failed without         | Read the job's output       |
|                           | continue-on-error            |                             |
+---------------------------+------------------------------+-----------------------------+

report_images

$report->report_images($records);

Prints one line per image for a preflight that reports as text: what needs it, and whether it was pulled, present, failed, skipped or deferred. A deferred image is named by its expression and followed by the reason it could not be resolved, which the reader could not see otherwise. Returns the object.

EXAMPLE

$report->report_images($prepared->{records});
#   image     perl:5.40 (job 'test'): present

API SPECIFICATION

INPUT

{ records => { type => 'arrayref' } }

OUTPUT

{ type => 'object', isa => 'App::Actions::Runner::Report' }

MESSAGES

None.

keep_logs

$report->keep_logs($logs, $root, $run_id);

Copies the logs of a parallel run into a directory of their own under $root, named after the time and the run, so --keep is not needed to read them afterwards. A failure to copy them is a warning, not a failed run. Returns the object.

ARGUMENTS

SIDE EFFECTS

Creates a private directory and copies the logs into it; warns on failure.

EXAMPLE

$report->keep_logs($parallel->logs, App::Actions::Logs->default_directory, $run_id);

API SPECIFICATION

INPUT

{
	logs   => { type => 'arrayref' },
	root   => { type => 'string', min => 1 },
	run_id => { type => 'string', min => 1 },
}

OUTPUT

{ type => 'object', isa => 'App::Actions::Runner::Report' }

MESSAGES

+-----------------------------+------------------------------+-----------------------------+
| Message                     | Meaning                      | Resolution                  |
+-----------------------------+------------------------------+-----------------------------+
| action-run: could not keep  | The directory could not be   | Check the log directory's   |
| the job logs: cannot copy   | made or a log copied         | space and permissions       |
| 'X' to 'Y': E               |                              |                             |
+-----------------------------+------------------------------+-----------------------------+

Says where "keep_logs" put the logs, one line per job, unless quiet or there were none. Returns the object.

EXAMPLE

$report->print_kept_logs;
#
# Job logs kept in /home/me/.cache/action-run/logs/20260101-120000-65f0a1b2c3
#   test (n=one)  job-1-test_n_one.log

API SPECIFICATION

INPUT

{}

OUTPUT

{ type => 'object', isa => 'App::Actions::Runner::Report' }

MESSAGES

None.

$report->print_kept_resources($containers->remaining, $temp_path);

Lists what a run that keeps its resources left behind: its containers, its networks and its temporary directory, so they can be inspected and removed. Prints nothing when there are none. Returns the object.

EXAMPLE

$report->print_kept_resources({ containers => [ 'c1' ], networks => [ 'n1' ] }, '/tmp/x');
# Kept Docker resources:
#   container c1
#   network n1
#   temporary directory /tmp/x

API SPECIFICATION

INPUT

{
	remaining => { type => 'hashref' },
	temp_path => { type => 'string', optional => 1 },
}

OUTPUT

{ type => 'object', isa => 'App::Actions::Runner::Report' }

MESSAGES

None.

LIMITATIONS

  • The summary's name column is 50 characters wide; a longer matrix name pushes its result to the right.

  • Logs are kept by copying, so a run's logs briefly take twice the space. Nothing expires them except action-run logs --prune.

  • The result words (success, failure, skipped) and image statuses are left untranslated: they are values a script may match, as in the JSON documents.

COMMON PITFALLS

  • Recording a failure does not fail the workflow. Call "fail" too; "print_summary" and "failed" look only at that flag. A skipped job does not fail the workflow.

  • Most methods return the object, so calls can be chained; but "failed" returns 0 or 1.

  • Kept logs are copies. They stay until you run action-run logs --prune.

  • quiet hides the summary and the log list, but not "report_images" output for a text preflight.

  • Result words are not translated. success, failure and skipped stay in English so scripts can match them.

ENCODING

Job names in the summary are Perl character strings and may contain any Unicode text, including emoji; the name column is counted in characters, so wide characters (such as Chinese) can make the columns uneven. Messages are in the language of App::Actions::I18N. Paths of kept logs are shown as bytes. Log files are copied byte for byte.

AUTHOR

Nigel Horne, njh@nigelhorne.com

FORMAL SPECIFICATION

This section describes each routine in the Z notation, a precise mathematical language for describing programs. You do not need to read it to use the module: everything here is also said in plain words in the sections above. It is for readers who want to check exactly what a routine promises.

How to read it: a box names a routine. The lines above the middle rule declare the values it works with. A name ending in ? is an input, and a name ending in ! is an output. ΞState means the routine changes nothing, and ΔState means it can change the state. The lines below the rule are the conditions that are always true when the routine returns.

new

┌─ Report ──────────────────────────────────────────
│ entries : seq (NAME × RESULT)
│ failed  : 𝔹
│ kept    : optional DIRECTORY
├───────────────────────────────────────────────────
│ true
└───────────────────────────────────────────────────

record

┌─ Record ──────────────────────────────────────────
│ ΔReport
│ name?, result? : seq CHAR
├───────────────────────────────────────────────────
│ entries' = entries ⁀ ⟨(name?, result?)⟩
└───────────────────────────────────────────────────

fail

┌─ Fail ────────────────────────────────────────────
│ ΔReport
├───────────────────────────────────────────────────
│ failed' = true
└───────────────────────────────────────────────────

failed

┌─ Failed ──────────────────────────────────────────
│ ΞReport
│ f! : 𝔹
├───────────────────────────────────────────────────
│ f! = failed
└───────────────────────────────────────────────────
┌─ PrintSummary ────────────────────────────────────
│ ΞReport
│ out! : seq LINE
├───────────────────────────────────────────────────
│ quiet ⇒ out! = ⟨⟩
│ ¬ quiet ⇒ out! = ⟨header⟩ ⁀ lines(entries) ⁀ ⟨verdict(failed)⟩
└───────────────────────────────────────────────────

report_images

┌─ ReportImages ────────────────────────────────────
│ ΞReport
│ records? : seq RECORD
│ out!     : seq LINE
├───────────────────────────────────────────────────
│ #out! = #records? + #{r : ran records? | r.status = deferred ∧ r.error ≠ ⊥}
└───────────────────────────────────────────────────

keep_logs

┌─ KeepLogs ────────────────────────────────────────
│ ΔReport
│ logs? : seq LOG
├───────────────────────────────────────────────────
│ let present == {l : ran logs? | exists(l.path)}
│ present = ∅ ⇒ kept' = kept
│ present ≠ ∅ ∧ copied ⇒ kept' = dir ∧ files(dir) = {l.file | l ∈ present}
└───────────────────────────────────────────────────
┌─ PrintKeptLogs ───────────────────────────────────
│ ΞReport
├───────────────────────────────────────────────────
│ quiet ∨ kept = ⊥ ⇒ out! = ⟨⟩
└───────────────────────────────────────────────────
┌─ PrintKeptResources ──────────────────────────────
│ ΞReport
│ r?    : RESOURCES
│ temp? : optional DIRECTORY
├───────────────────────────────────────────────────
│ r?.containers = ⟨⟩ ∧ r?.networks = ⟨⟩ ∧ temp? = ⊥ ⇒ out! = ⟨⟩
└───────────────────────────────────────────────────

STATE DIAGRAM

The report collects results while the run goes on, then prints once:

  new(docker => ..., quiet => ...)
        |
        v
+-------------------+  record($name, 'success' | 'skipped')
| COLLECTING        |<---------------------------------------+
| (flag: passing)   |                                        |
+-------------------+                                        |
        |     |  record($name, 'failure')->fail               |
        |     v                                               |
        |  +-------------------+  more record() calls ------->+
        |  | COLLECTING        |
        |  | (flag: failed)    |  failed() = 1 from now on
        |  +-------------------+
        |     |
        +--+--+
           |  preflight with text output: report_images($records)
           |  (one line per image: pulled / present / failed /
           |   skipped / deferred)
           v
+-------------------+  parallel run: keep_logs($logs, $root, $run_id)
| RUN FINISHED      |  copies logs to $root/DATE-TIME-RUNID/
+-------------------+  (a copy error is only a warning)
           |
           v
+-------------------+  "=== Workflow summary ===", one line per
| print_summary     |  job, then "Workflow succeeded" or
+-------------------+  "Workflow FAILED" (unless quiet)
           |
           +--> print_kept_logs: where the logs are (unless quiet)
           +--> print_kept_resources: containers, networks and
                temporary folder left by a run with keep

LICENSE AND COPYRIGHT

Copyright 2026 Nigel Horne.

This program is released under the GNU General Public License, version 2. See the LICENSE file that comes with this distribution. If you use it, please let me know.