pytooling-gitlab
pyTooling.GitLab installs a program: pytooling-gitlab. It is the command line front-end to what the package models - reading a GitLab CI/CD pipeline into a trace, writing that trace, rendering it - so a pipeline job can do those things without a script of its own.
pytooling-gitlab help # what the program can do
pytooling-gitlab help <command> # what one command can do
pytooling-gitlab version # which pyTooling.GitLab is installed
The program is the console_scripts entry point pyTooling.GitLab.CLI:main, which setup.py
registers, so it is on the path after pip install pyTooling.GitLab.
Attention
Drawing needs the diagram extra: pip install pyTooling.GitLab[diagram] installs
matplotlib, which --gantt draws with. Without it, --gantt names the missing package and the
command line installing it rather than failing on an import. colorama, which a
TerminalApplication needs to write anything at all, comes with the package’s
requirement pyTooling[terminal].
How a command is declared
The program is a TerminalApplication and an
ArgParseHelperMixin, so it prints like the first and parses like the
second: a command is a method marked with CommandHandler, and the
arguments of that command are the attributes written above the method. See ArgParse for the attributes
themselves.
@CommandHandler("version", help="Display version information.")
def HandleVersion(self, _: Namespace) -> None:
...
Two commands are always there: help, which prints the help page of the program or of one command, and
version. A call with no command prints the help page.
A group of related commands is a mixin-class of its own.
Application inherits from all of them, and the attributes are found on the assembled
class, so adding a command means writing a mixin and adding one base-class - see Split Handlers into multiple classes. That is the
same construction pyedaa-outputfilter uses.
Hint
The three global switches - -q / --quiet, -v / --verbose and -d / --debug - are declared
on the default handler, so they belong before the command: pytooling-gitlab --verbose version, not
pytooling-gitlab version --verbose.
What a failure looks like
main() runs the program inside a try ... except, so a user of the program sees a message
and a non-zero exit code rather than a traceback. A ToolingException is printed with
its cause and with every note it carries, because the notes are where pyTooling puts the advice - “check the
project’s path or identifier” rather than only “404”.
A MissingDependencyError is not handled where it is raised. An output needing an
optional package - --gantt needs matplotlib - imports it where it draws and lets the exception travel to
main(), which hands it to
PrintMissingDependencyError(): that printer names the missing
package and every command line installing it, and reports no bug, because nothing is wrong with the program.
The pipeline command
pytooling-gitlab pipeline reads one CI pipeline into a Trace and writes what was
asked of it. Reading and writing are separate steps: the trace is the intermediate every output is derived from, so a
further output is a further option rather than a second reader.
pytooling-gitlab pipeline --gitlab-project=gitlab-org/gitlab-development-kit \
--gitlab-pipeline-id=2954836704 \
--trace-file=report/Pipeline.otlp.json \
--gantt=report/Pipeline.png
Option |
Meaning |
|---|---|
|
The project the pipeline belongs to, as its path - |
|
The pipeline to read - the number in its URL, |
|
GitLab’s REST API, e.g. of a self-managed instance. Default: |
|
The token reading the pipeline. Default: |
|
Write the trace. Default format: |
|
Draw the pipeline as a Gantt chart: |
|
Overwrite files that exist. Without it, an existing file is an error. |
The token is --token, else $GITLAB_TOKEN - the option wins, e.g. to read another instance’s pipeline from
inside a pipeline holding its own token. A public project’s pipelines are read without one; a private project’s need a
token with the read_api scope - inside a pipeline, e.g. a project access token stored as a masked CI/CD variable;
$CI_JOB_TOKEN can’t read pipelines - GitLab lets it update a pipeline’s metadata only. The child pipelines are read
too; a multi-project pipeline’s jobs aren’t, as they belong to another project.
Hint
A job cannot see itself. It is still running when it reads the pipeline, so a job that reports the pipeline’s timing needs every other job - e.g. in a stage of its own, the last one - and runs last.
[<format>:]<file>
Every option that writes a file takes the format in front of the path, separated by a colon, and assumes a
default when none is given - --trace-file=report/Pipeline.otlp.json and
--trace-file=otlp-json:report/Pipeline.otlp.json are the same thing.
A colon alone doesn’t make a format. A format is more than one character long and holds no path separator, so
a Windows drive (C:\report\trace.json) and a colon deeper down a path are paths. A prefix that looks
like a format but isn’t one is an error naming the formats that exist, rather than a file with a strange name.
None of that is the command’s own: splitFormat() does the splitting for any
program declaring an option of this shape, and a value naming no format gets the enumeration’s DEFAULT - see
pyTooling’s argument formats.
Both are checked before the pipeline is read, so a misspelled format or a file that exists is reported at once instead of after a network round-trip.
Drawing the pipeline
--gantt lays the trace out as a Gantt chart
and draws it: one row per job, the time it was queued for a runner in light gray in front of the time it ran, and a line
for the pipeline, every matrix and every downstream pipeline. A job is colored by its runner - its first tag, or the
runner’s description -, and the legend carries the statistics per runner (see Gantt Chart).
A format is the backend and the file format - matplotlib-png, matplotlib-svg, matplotlib-pdf - and the
file’s suffix has to agree with it. A value naming no format gets matplotlib-png, so a PNG needs no format, and
any other file states it: --gantt=matplotlib-svg:report/Pipeline.svg. A suffix that disagrees with the
format is reported rather than a file being written under a name that lies about its content.
matplotlib is an optional dependency. Without it, --gantt reports which extra installs it
(pyTooling[diagram]) instead of failing on an import.
Hint
In an SVG, every bar or line is a group named after the timespan it draws - span-<SpanID> - so a script can
find the elements of a job.
pyTooling’s rendering section describes
the identifiers.