[CI:DOCS] man-page checker: include --format (Go templates)

Very belated successor to #14046.

I don't know why this is so important to me. Probably because we're
doing a halfhearted sloppy job of documenting, and new options get
added, and not documented, and that's just wrong.

I've given up on documenting internal structs. This iteration
has a $Format_Exceptions table defined at the top of the xref
script, enumerating a hardcoded defined set of podman commands
and fields that should remain undocumented.

This iteration also forgives completely-undocumented formats.
If podman-foo has a --format, but podman-foo.1.md does not
list *any* valid fields, the script warns but does not fail.
This at least is better than documenting a random mix of fields.

This version of the xref script is much slower: 10s vs 4. I
think we can live with that in a CI-only script.

Signed-off-by: Ed Santiago <santiago@redhat.com>
This commit is contained in:
Ed Santiago
2023-02-08 09:42:07 -07:00
parent d1fd399455
commit 4334135491
11 changed files with 271 additions and 85 deletions

View File

@ -61,12 +61,12 @@ Valid placeholders for the Go template are listed below:
| **Placeholder** | **Description** |
| --------------- | -------------------------------------- |
| .Unit | Name of the systemd unit |
| .ContainerName | Name of the container |
| .ContainerID | ID of the container |
| .Container | ID and name of the container |
| .ContainerID | ID of the container |
| .ContainerName | Name of the container |
| .Image | Name of the image |
| .Policy | Auto-update policy of the container |
| .Unit | Name of the systemd unit |
| .Updated | Update status: true,false,failed |
#### **--rollback**

View File

@ -38,6 +38,7 @@ Valid placeholders for the Go template are listed below:
| .HostsPath | Path to container /etc/hosts file (string) |
| .ID | Container ID (full 64-char hash) |
| .Image | Container image ID (64-char hash) |
| .ImageDigest | Container image digest (sha256:+64-char hash) |
| .ImageName | Container image name (string) |
| .IsInfra | Is this an infra container? (string: true/false) |
| .IsService | Is this a service container? (string: true/false) |

View File

@ -101,19 +101,20 @@ In the case where an ID is used, the ID may be in its full or shortened form. T
Format the output to JSON Lines or using the given Go template.
| **Placeholder** | **Description** |
|--------------------|-----------------------------------------------|
| .Attributes | created_at, _by, labels, and more (map[]) |
| .ContainerExitCode | Exit code (int) |
| .Details ... | Internal structure, not actually useful |
| .HealthStatus | Health Status (string) |
| .ID | Container ID (full 64-bit SHA) |
| .Image | Name of image being run (string) |
| .Name | Container name (string) |
| .Network | Name of network being used (string) |
| .Status | Event status (e.g., create, start, died, ...) |
| .Time | Event timestamp (string) |
| .Type | Event type (e.g., image, container, pod, ...) |
| **Placeholder** | **Description** |
|-----------------------|-----------------------------------------------|
| .Attributes | created_at, _by, labels, and more (map[]) |
| .ContainerExitCode | Exit code (int) |
| .ContainerInspectData | Payload of the container's inspect |
| .HealthStatus | Health Status (string) |
| .ID | Container ID (full 64-bit SHA) |
| .Image | Name of image being run (string) |
| .Name | Container name (string) |
| .Network | Name of network being used (string) |
| .PodID | ID of pod associated with container, if any |
| .Status | Event status (e.g., create, start, died, ...) |
| .Time | Event timestamp (string) |
| .Type | Event type (e.g., image, container, pod, ...) |
#### **--help**

View File

@ -25,16 +25,16 @@ Alter the output for a format like 'json' or a Go template.
Valid placeholders for the Go template are listed below:
| **Placeholder** | **Description** |
| --------------- | ----------------------------------------------------------------------------- |
| .ID | Image ID |
| .Created | if --human, time elapsed since creation, otherwise time stamp of creation |
| .CreatedAt | Time when the image layer was created |
| .CreatedBy | Command used to create the layer |
| .CreatedSince | Elapsed time since the image layer was created |
| .Size | Size of layer on disk |
| .Comment | Comment for the layer |
| .Tags | Image tags |
| **Placeholder** | **Description** |
|------------------------|---------------------------------------------------------------------------|
| .ID | Image ID |
| .Created | if --human, time elapsed since creation, otherwise time stamp of creation |
| .CreatedAt | Time when the image layer was created |
| .CreatedBy | Command used to create the layer |
| .CreatedSince | Elapsed time since the image layer was created |
| .Size | Size of layer on disk |
| .Comment | Comment for the layer |
| .Tags | Image tags |
#### **--help**, **-h**

View File

@ -88,7 +88,6 @@ Valid placeholders for the Go template are listed below:
| .History | History of the image layer |
| .ID | Image ID (truncated) |
| .Id | Image ID (full SHA) |
| .ImageSummary | Internal data structure, not actually useful |
| .IsDangling | Is image dangling? (true/false) |
| .IsReadOnly | Is unage read-only? (true/false) |
| .Labels | map[] of labels |

View File

@ -14,20 +14,21 @@ Display the (JSON format) network configuration.
Pretty-print networks to JSON or using a Go template.
| **Placeholder** | **Description** |
| ----------------- | ----------------------------------------- |
| .ID | Network ID |
| .Name | Network name |
| .Driver | Network driver |
| .Labels | Network labels |
| .Options | Network options |
| .IPAMOptions | Network ipam options |
| .Created | Timestamp when the network was created |
| .Internal | Network is internal (boolean) |
| .IPv6Enabled | Network has ipv6 subnet (boolean) |
| .DNSEnabled | Network has dns enabled (boolean) |
| .NetworkInterface | Name of the network interface on the host |
| .Subnets | List of subnets on this network |
| **Placeholder** | **Description** |
|--------------------|-------------------------------------------|
| .ID | Network ID |
| .Name | Network name |
| .Driver | Network driver |
| .Labels | Network labels |
| .Options | Network options |
| .IPAMOptions | Network ipam options |
| .Created | Timestamp when the network was created |
| .Internal | Network is internal (boolean) |
| .IPv6Enabled | Network has ipv6 subnet (boolean) |
| .DNSEnabled | Network has dns enabled (boolean) |
| .NetworkDNSServers | Array of DNS servers used in this network |
| .NetworkInterface | Name of the network interface on the host |
| .Subnets | List of subnets on this network |
## EXAMPLE

View File

@ -42,20 +42,21 @@ Change the default output format. This can be of a supported type like 'json'
or a Go template.
Valid placeholders for the Go template are listed below:
| **Placeholder** | **Description** |
| ----------------- | ----------------------------------------- |
| .ID | Network ID |
| .Name | Network name |
| .Driver | Network driver |
| .Labels | Network labels |
| .Options | Network options |
| .IPAMOptions | Network ipam options |
| .Created | Timestamp when the network was created |
| .Internal | Network is internal (boolean) |
| .IPv6Enabled | Network has ipv6 subnet (boolean) |
| .DNSEnabled | Network has dns enabled (boolean) |
| .NetworkInterface | Name of the network interface on the host |
| .Subnets | List of subnets on this network |
| **Placeholder** | **Description** |
|--------------------|-------------------------------------------|
| .ID | Network ID |
| .Name | Network name |
| .Driver | Network driver |
| .Labels | Network labels |
| .Options | Network options |
| .IPAMOptions | Network ipam options |
| .Created | Timestamp when the network was created |
| .Internal | Network is internal (boolean) |
| .IPv6Enabled | Network has ipv6 subnet (boolean) |
| .DNSEnabled | Network has dns enabled (boolean) |
| .NetworkDNSServers | Array of DNS servers used in this network |
| .NetworkInterface | Name of the network interface on the host |
| .Subnets | List of subnets on this network |
#### **--no-trunc**

View File

@ -77,17 +77,20 @@ Pretty-print containers to JSON or using a Go template
Valid placeholders for the Go template are listed below:
| **Placeholder** | **Description** |
| ------------------- | ----------------------------------------------------------------------------------------------- |
| .ID | Container ID |
| .Name | Name of pod |
| .Status | Status of pod |
| .Labels | All the labels assigned to the pod |
| .NumberOfContainers | Show the number of containers attached to pod |
| .Cgroup | Cgroup path of pod |
| .Created | Creation time of pod |
| .InfraID | Pod infra container ID |
| .Networks | Show all networks connected to the infra container |
| **Placeholder** | **Description** |
|---------------------|----------------------------------------------------|
| .Cgroup | Cgroup path of pod |
| .ContainerIds | Comma-separated list of container IDs in the pod |
| .ContainerNames | Comma-separated list of container names in the pod |
| .ContainerStatuses | Comma-separated list of container statuses |
| .Created | Creation time of pod |
| .ID | Container ID |
| .InfraID | Pod infra container ID |
| .Labels | All the labels assigned to the pod |
| .Name | Name of pod |
| .Networks | Show all networks connected to the infra container |
| .NumberOfContainers | Show the number of containers attached to pod |
| .Status | Status of pod |
#### **--help**, **-h**

View File

@ -67,22 +67,33 @@ Pretty-print containers to JSON or using a Go template
Valid placeholders for the Go template are listed below:
| **Placeholder** | **Description** |
| --------------- | ------------------------------------------------ |
| .ID | Container ID |
| .Image | Image Name/ID |
| .ImageID | Image ID |
| .Command | Quoted command used |
| .CreatedAt | Creation time for container |
| .RunningFor | Time elapsed since container was started |
| .Status | Status of container |
| .Pod | Pod the container is associated with |
| .Ports | Exposed ports |
| .Size | Size of container |
| .Names | Name of container |
| .Networks | Show all networks connected to the container |
| .Labels | All the labels assigned to the container |
| .Mounts | Volumes mounted in the container |
| **Placeholder** | **Description** |
|--------------------|----------------------------------------------|
| .AutoRemove | If true, container will be removed on exit |
| .Command | Quoted command used |
| .Created | Creation time for container, Y-M-D H:M:S |
| .CreatedAt | Creation time for container (same as above) |
| .CreatedHuman | Creation time, relative |
| .ExitCode | Container exit code |
| .Exited | "true" if container has exited |
| .ExitedAt | Time (epoch seconds) that container exited |
| .ID | Container ID |
| .Image | Image Name/ID |
| .ImageID | Image ID |
| .IsInfra | "true" if infra container |
| .Labels | All the labels assigned to the container |
| .Mounts | Volumes mounted in the container |
| .Names | Name of container |
| .Networks | Show all networks connected to the container |
| .Pid | Process ID on host system |
| .Pod | Pod the container is associated with (SHA) |
| .PodName | Seems to be empty no matter what |
| .Ports | Exposed ports |
| .RunningFor | Time elapsed since container was started |
| .Size | Size of container |
| .StartedAt | Time (epoch seconds) the container started |
| .State | Human-friendly description of ctr state |
| .Status | Status of container |
#### **--help**, **-h**

View File

@ -20,13 +20,14 @@ Secrets can be queried individually by providing their full name or a unique par
Format secret output using Go template.
| **Placeholder** | **Description** |
| ------------------------ | ----------------------------------------------------------------- |
|--------------------------|-------------------------------------------------------------------|
| .CreatedAt | When secret was created (relative timestamp, human-readable) |
| .ID | ID of secret |
| .Spec | Details of secret |
| .Spec.Driver | Driver info |
| .Spec.Driver.Name | Driver name (string) |
| .Spec.Driver.Options ... | Driver options (map of driver-specific options) |
| .Spec.Labels | Labels for this secret |
| .Spec.Name | Name of secret |
| .UpdatedAt | When secret was last updated (relative timestamp, human-readable) |

View File

@ -35,6 +35,43 @@ my $Markdown_Path = "$Docs_Path/markdown";
# Global error count
my $Errs = 0;
# Table of exceptions for documenting fields in '--format {{.Foo}}'
#
# Autocomplete is wonderful, and it's even better when we document the
# existing options. Unfortunately, sometimes internal structures get
# exposed that are of no use to anyone and cannot be guaranteed. Avoid
# documenting those. This table lists those exceptions. Format is:
#
# foo .Bar
#
# ...such that "podman foo --format '{{.Bar}}'" will not be documented.
#
my $Format_Exceptions = <<'END_EXCEPTIONS';
# Deep internal structs; pretty sure these are permanent exceptions
events .Details
history .ImageHistoryLayer
images .ImageSummary
network-ls .Network
# FIXME: this one, maybe? But someone needs to write the text
machine-list .Starting
# No clue what these are. Some are just different-case dups of others.
pod-ps .Containers .Id .InfraId .ListPodsReport .Namespace
ps .Cgroup .CGROUPNS .IPC .ListContainer .MNT .Namespaces .NET .PIDNS .User .USERNS .UTS
# I think .Destination is an internal struct, but .IsMachine maybe needs doc?
system-connection-list .Destination .IsMachine
END_EXCEPTIONS
my %Format_Exceptions;
for my $line (split "\n", $Format_Exceptions) {
$line =~ s/#.*$//; # strip comments
next unless $line; # skip empty lines
my ($subcommand, @fields) = split(' ', $line);
$Format_Exceptions{"podman-$subcommand"} = \@fields;
}
# END user-customizable section
###############################################################################
@ -126,14 +163,38 @@ sub main {
sub xref_by_help {
my ($help, $man, @subcommand) = @_;
OPTION:
for my $k (sort keys %$help) {
if (exists $man->{$k}) {
if (ref $help->{$k}) {
# This happens when 'podman foo --format' offers
# autocompletion that looks like a Go template, but those
# template options aren't documented in the man pages.
if ($k eq '--format' && ! ref($man->{$k})) {
warn "$ME: 'podman @subcommand': --format options are available through autocomplete, but are not documented in $man->{_path}\n";
# FIXME: don't make this an error... yet.
# ++$Errs;
next OPTION;
}
xref_by_help($help->{$k}, $man->{$k}, @subcommand, $k);
}
# Otherwise, non-ref is leaf node such as a --option
}
else {
# Not documented in man. However, handle '...' as a special case
# in formatting strings. E.g., 'podman info .Host' is documented
# in the man page as '.Host ...' to indicate that the subfields
# are way too many to list individually.
my $k_copy = $k;
while ($k_copy =~ s/\.[^.]+$//) {
if (($man->{$k_copy}||'') eq '...') {
next OPTION;
}
}
# Nope, it's not that case.
my $man = $man->{_path} || 'man';
warn "$ME: 'podman @subcommand --help' lists '$k', which is not in $man\n";
++$Errs;
@ -152,11 +213,23 @@ sub xref_by_man {
my ($help, $man, @subcommand) = @_;
# FIXME: this generates way too much output
KEYWORD:
for my $k (grep { $_ ne '_path' } sort keys %$man) {
if ($k eq '--format' && ref($man->{$k}) && ! ref($help->{$k})) {
warn "$ME: 'podman @subcommand': --format options documented in man page, but not available via autocomplete\n";
next KEYWORD;
}
if (exists $help->{$k}) {
if (ref $man->{$k}) {
xref_by_man($help->{$k}, $man->{$k}, @subcommand, $k);
}
elsif ($k =~ /^-/) {
# This is OK: we don't recurse into options
}
else {
# FIXME: should never get here, but we do. Figure it out later.
}
}
elsif ($k ne '--help' && $k ne '-h') {
my $man = $man->{_path} || 'man';
@ -254,15 +327,39 @@ sub podman_help {
}
}
elsif ($section eq 'options') {
my $opt = '';
# Handle '--foo' or '-f, --foo'
if ($line =~ /^\s{1,10}(--\S+)\s/) {
print "> podman @_ $1\n" if $debug;
$help{$1} = 1;
$opt = $1;
$help{$opt} = 1;
}
elsif ($line =~ /^\s{1,10}(-\S),\s+(--\S+)\s/) {
print "> podman @_ $1, $2\n" if $debug;
$help{$1} = $help{$2} = 1;
$opt = $2;
$help{$1} = $help{$opt} = 1;
}
# Special case for --format: run podman with autocomplete.
# If that lists one or more '{{.Foo}}' or '{{.Foo.' entries
# (indicating terminal or nonterminal nodes respectively)
# convert our option data structure from scalar (indicating
# that we just cross-check for existence in the man page)
# to hashref (indicating that we recurse down and cross-check
# each individual param).
if ($opt eq '--format') {
my @completions = _completions(@_, '--format', '{{.');
for my $c (@completions) {
if ($c =~ /^\{\{(\.\S+)(\.|\}\})$/) {
# First time through: convert to a hashref
$help{$opt} = {} if ! ref($help{$opt});
# Remember this param
$help{$opt}{$1} = 1;
}
}
}
}
}
close $fh
@ -420,6 +517,31 @@ sub podman_man {
}
}
}
# --format does not always mean a Go format! E.g., push --format=oci
if ($previous_flag eq '--format') {
# ...but if there's a table like '| .Foo | blah blah |'
# then it's definitely a Go template. '.Foo ...' (three dots)
# indicates that .Foo includes a number of subfields .Foo.Xxx,
# .Foo.Yyy too numerous to list individually in man pages.
# 1 12 3 32
if ($line =~ /^\|\s+(\.\S+)(\s+(\.\.\.))?\s+\|/) {
my ($format, $etc) = ($1, $3);
# Confirmed: we have a table with '.Foo' strings, so
# this is a Go template. Override previous (scalar)
# setting of the --format flag with a hash, indicating
# that we will recursively cross-check each param.
if (! ref($man{$previous_flag})) {
$man{$previous_flag} = { _path => $subpath };
}
# ...and document this format option. $etc, if set,
# will be '...' which indicates that $format has
# too many subformats to document individually.
$man{$previous_flag}{$format} = $etc || 1;
}
}
}
# It's easy to make mistakes in the SEE ALSO elements.
@ -429,6 +551,13 @@ sub podman_man {
}
close $fh;
# Done reading man page. If there are any '--format' exceptions defined
# for this command, flag them as seen, and as '...' so we don't
# complain about any sub-fields.
if (my $fields = $Format_Exceptions{$command}) {
$man{"--format"}{$_} = '...' for @$fields;
}
# Special case: the 'image trust' man page tries hard to cover both set
# and show, which means it ends up not being machine-readable.
if ($command eq 'podman-image-trust') {
@ -511,6 +640,45 @@ sub podman_rst {
return \%rst;
}
##################
# _completions # run podman __completeNoDesc, return list of completions
##################
sub _completions {
my $kidpid = open my $podman_fh, '-|';
if (! defined $kidpid) {
die "$ME: Could not fork: $!\n";
}
if ($kidpid == 0) {
# We are the child
close STDERR;
exec $PODMAN, '__completeNoDesc', @_;
die "$ME: Could not exec: $!\n";
}
# We are the parent
my @completions;
while (my $line = <$podman_fh>) {
chomp $line;
push @completions, $line;
# Recursively expand Go templates, like '{{.Server.Os}}'
if ($line =~ /^\{\{\..*\.$/) {
my @cmd_copy = @_; # clone of podman subcommands...
pop @cmd_copy; # ...so we can recurse with new format
my @subcompletions = _completions(@cmd_copy, $line);
# A huge number of deep fields are time-related. Don't document them.
my @is_time = grep { /Nanosecond|UnixNano|YearDay/ } @subcompletions;
push @completions, @subcompletions
unless @is_time >= 3;
}
}
close $podman_fh
or warn "$ME: Error running podman __completeNoDesc @_\n";
return @completions;
}
# END data gathering
###############################################################################
# BEGIN sanity checking of SEE ALSO links