Man pages: tighter documenting of --format fields

Initial impetus was #20958 (ps --format .Label abc). This is
a complicated solution to a simple-seeming problem.

The problem: .Label is a cobra *function*, something I did not
know about nor handle.

Solution: recognize cobra functions. Switch to __complete,
not __completeNoDesc, so we can see the number of arguments
required. Invent new man-page format for documenting functions.
And, finally, start enforcing how functions (and cobra structs)
are documented.

This discovered a never-used completion function, .Recycle(),
in podman-events. Remove it.

[NO NEW TESTS NEEDED] - the .go change is an excision of dead code.

Signed-off-by: Ed Santiago <santiago@redhat.com>
This commit is contained in:
Ed Santiago
2023-12-14 11:28:30 -07:00
parent 3a46fe858f
commit dbe0e67897
17 changed files with 155 additions and 77 deletions

View File

@ -27,7 +27,7 @@ Valid placeholders for the Go template are listed below:
| .BoundingCaps | Bounding capability set (array of strings) |
| .Config ... | Structure with config info |
| .ConmonPidFile | Path to file containing conmon pid (string) |
| .Created | Container creation time (string, ISO3601) |
| .Created ... | Container creation time (string, ISO3601) |
| .Dependencies | Dependencies (array of strings) |
| .Driver | Storage driver (string) |
| .EffectiveCaps | Effective capability set (array of strings) |

View File

@ -104,20 +104,21 @@ 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) |
| .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, ...) |
| **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) |
| .ToHumanReadable *bool* | If true, truncates CID in output |
| .Type | Event type (e.g., image, container, pod, ...) |
#### **--help**

View File

@ -21,18 +21,18 @@ Valid placeholders for the Go template are listed below:
| **Placeholder** | **Description** |
| ----------------- | ------------------ |
| .Annotations | Annotation information included in the image |
| .Annotations ... | Annotation information included in the image |
| .Architecture | Architecture of software in the image |
| .Author | Image author |
| .Comment | Image comment |
| .Config ... | Structure with config info |
| .Created | Image creation time (string, ISO3601) |
| .Created ... | Image creation time (string, ISO3601) |
| .Digest | Image digest (sha256:+64-char hash) |
| .GraphDriver ... | Structure for the graph driver info |
| .HealthCheck ... | Structure for the health check info |
| .History | History information stored in image |
| .ID | Image ID (full 64-char hash) |
| .Labels | Label information included in the image |
| .Labels ... | Label information included in the image |
| .ManifestType | Manifest type of the image |
| .NamesHistory | Name history information stored in image |
| .Os | Operating system of software in the image |

View File

@ -90,7 +90,7 @@ Valid placeholders for the Go template are listed below:
| .Id | Image ID (full SHA) |
| .IsDangling | Is image dangling? (true/false) |
| .IsReadOnly | Is unage read-only? (true/false) |
| .Labels | map[] of labels |
| .Labels ... | map[] of labels |
| .Names | Image FQIN |
| .ParentId | Full SHA of parent image ID, or null (string) |
| .ReadOnly | Same as .IsReadOnly |

View File

@ -27,14 +27,14 @@ Print results with a Go template.
| ------------------- | --------------------------------------------------------------------- |
| .ConfigPath ... | Machine configuration file location |
| .ConnectionInfo ... | Machine connection information |
| .Created | Machine creation time (string, ISO3601) |
| .Created ... | Machine creation time (string, ISO3601) |
| .Image ... | Machine image config |
| .LastUp | Time when machine was last booted |
| .LastUp ... | Time when machine was last booted |
| .Name | Name of the machine |
| .Resources ... | Resources used by the machine |
| .Rootful | Whether the machine prefers rootful or rootless container execution |
| .SSHConfig ... | SSH configuration info for communicating with machine |
| .State ... | Machine state |
| .State | Machine state |
| .UserModeNetworking | Whether this machine uses user-mode networking |
#### **--help**

View File

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

View File

@ -44,18 +44,18 @@ Valid placeholders for the Go template are listed below:
| **Placeholder** | **Description** |
|--------------------|-------------------------------------------|
| .Created | Timestamp when the network was created |
| .Created ... | Timestamp when the network was created |
| .DNSEnabled | Network has dns enabled (boolean) |
| .Driver | Network driver |
| .ID | Network ID |
| .Internal | Network is internal (boolean) |
| .IPAMOptions | Network ipam options |
| .IPAMOptions ... | Network ipam options |
| .IPv6Enabled | Network has ipv6 subnet (boolean) |
| .Labels | Network labels |
| .Name | Network name |
| .NetworkDNSServers | Array of DNS servers used in this network |
| .NetworkInterface | Name of the network interface on the host |
| .Options | Network options |
| .Options ... | Network options |
| .Routes | List of static routes for this network |
| .Subnets | List of subnets on this network |

View File

@ -34,7 +34,7 @@ Valid placeholders for the Go template are listed below:
| .CPUShares | CPU Shares |
| .CreateCgroup | Whether cgroup was created |
| .CreateCommand | Create command |
| .Created | Time when the pod was created |
| .Created ... | Time when the pod was created |
| .CreateInfra | Whether infrastructure created |
| .Devices | Devices |
| .ExitPolicy | Exit policy |
@ -43,7 +43,7 @@ Valid placeholders for the Go template are listed below:
| .InfraConfig ... | Infra config (contains further fields) |
| .InfraContainerID | Pod infrastructure ID |
| .InspectPodData ... | Nested structure, for experts only |
| .Labels | Pod labels |
| .Labels ... | Pod labels |
| .LockNumber | Number of the pod's Libpod lock |
| .MemoryLimit | Memory limit, bytes |
| .MemorySwap | Memory swap limit, in bytes |

View File

@ -86,7 +86,8 @@ Valid placeholders for the Go template are listed below:
| .Created | Creation time of pod |
| .ID | Container ID |
| .InfraID | Pod infra container ID |
| .Labels | All the labels assigned to the pod |
| .Label *string* | Specified label of the pod |
| .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 |

View File

@ -74,7 +74,7 @@ Valid placeholders for the Go template are listed below:
| .AutoRemove | If true, containers are removed on exit |
| .CIDFile | Container ID File |
| .Command | Quoted command used |
| .Created | Creation time for container, Y-M-D H:M:S |
| .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 |
@ -84,7 +84,8 @@ Valid placeholders for the Go template are listed below:
| .Image | Image Name/ID |
| .ImageID | Image ID |
| .IsInfra | "true" if infra container |
| .Labels | All the labels assigned to the container |
| .Label *string* | Specified label of the 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 |

View File

@ -21,16 +21,16 @@ Format secret output using Go template.
| **Placeholder** | **Description** |
|--------------------------|-------------------------------------------------------------------|
| .CreatedAt | When secret was created (relative timestamp, human-readable) |
| .CreatedAt ... | When secret was created (relative timestamp, human-readable) |
| .ID | ID of secret |
| .SecretData | Secret Data (Displayed only with --showsecret option) |
| .Spec ... | Details of secret |
| .Spec.Driver | Driver info |
| .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.Labels ... | Labels for this secret |
| .Spec.Name | Name of secret |
| .UpdatedAt | When secret was last updated (relative timestamp, human-readable) |
| .UpdatedAt ... | When secret was last updated (relative timestamp, human-readable) |
#### **--help**

View File

@ -32,16 +32,16 @@ Valid placeholders for the Go template are listed below:
| **Placeholder** | **Description** |
| ------------------------ | ----------------------------------------------------------------- |
| .CreatedAt | When secret was created (relative timestamp, human-readable) |
| .CreatedAt ... | When secret was created (relative timestamp, human-readable) |
| .ID | ID of secret |
| .SecretData | Secret Data (Displayed only with --showsecret option) |
| .Spec ... | Details of secret |
| .Spec.Driver | Driver info |
| .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.Labels ... | Labels for this secret |
| .Spec.Name | Name of secret |
| .UpdatedAt | When secret was last updated (relative timestamp, human-readable) |
| .UpdatedAt ... | When secret was last updated (relative timestamp, human-readable) |
@@option noheading

View File

@ -29,19 +29,19 @@ Valid placeholders for the Go template are listed below:
| **Placeholder** | **Description** |
| ------------------- | ------------------------------------------------------ |
| .Anonymous | Indicates whether volume is anonymous |
| .CreatedAt | Volume creation time |
| .CreatedAt ... | Volume creation time |
| .Driver | Volume driver |
| .GID | GID the volume was created with |
| .Labels | Label information associated with the volume |
| .Labels ... | Label information associated with the volume |
| .LockNumber | Number of the volume's Libpod lock |
| .MountCount | Number of times the volume is mounted |
| .Mountpoint | Source of volume mount point |
| .Name | Volume name |
| .NeedsChown | Indicates volume needs to be chowned on first use |
| .NeedsCopyUp | Indicates volume needs dest data copied up on first use|
| .Options | Volume options |
| .Options ... | Volume options |
| .Scope | Volume scope |
| .Status | Status of the volume |
| .Status ... | Status of the volume |
| .StorageID | StorageID of the volume |
| .Timeout | Timeout of the volume |
| .UID | UID the volume was created with |

View File

@ -43,20 +43,20 @@ Valid placeholders for the Go template are listed below:
| **Placeholder** | **Description** |
| ------------------------- | -------------------------------------------- |
| .Anonymous | Indicates whether volume is anonymous |
| .CreatedAt | Volume creation time |
| .CreatedAt ... | Volume creation time |
| .Driver | Volume driver |
| .GID | GID of volume |
| .InspectVolumeData ... | Don't use |
| .Labels | Label information associated with the volume |
| .Labels ... | Label information associated with the volume |
| .LockNumber | Number of the volume's Libpod lock |
| .MountCount | Number of times the volume is mounted |
| .Mountpoint | Source of volume mount point |
| .Name | Volume name |
| .NeedsChown | Indicates whether volume needs to be chowned |
| .NeedsCopyUp | Indicates if volume needs to be copied up to |
| .Options | Volume options |
| .Options ... | Volume options |
| .Scope | Volume scope |
| .Status | Status of the volume |
| .Status ... | Status of the volume |
| .StorageID | StorageID of the volume |
| .Timeout | Timeout of the volume |
| .UID | UID of volume |

View File

@ -228,7 +228,37 @@ sub xref_by_help {
xref_by_help($help->{$k}, $man->{$k}, @subcommand, $k);
}
# Otherwise, non-ref is leaf node such as a --option
# Documenting --format fields is tricky! They can be scalars, structs,
# or functions. This is a complicated block because if help & man don't
# match, we want to give the most user-friendly message possible.
elsif (@subcommand && $subcommand[-1] eq '--format') {
# '!' is one of the Format_Exceptions defined at top
if (($man->{$k} ne '!') && ($man->{$k} ne $help->{$k})) {
# Fallback message
my $msg = "TELL ED TO HANDLE THIS: man='$man->{$k}' help='$help->{$k}'";
# Many different permutations of mismatches.
my $combo = "$man->{$k}-$help->{$k}";
if ($combo eq '0-...') {
$msg = "is a nested structure. Please add '...' to man page.";
}
elsif ($combo =~ /^\d+-\.\.\.$/) {
$msg = "is a nested structure, but the man page documents it as a function?!?";
}
elsif ($combo eq '...-0') {
$msg = "is a simple value, not a nested structure. Please remove '...' from man page.";
}
elsif ($combo =~ /^0-[1-9]\d*$/) {
$msg = "is a function that calls for $help->{$k} args. Please investigate what those are, then add them to the man page. E.g., '$k *bool*' or '$k *path* *bool*'";
}
elsif ($combo =~ /^\d+-[1-9]\d*$/) {
$msg = "is a function that calls for $help->{$k} args; the man page lists $man->{$k}. Please fix the man page.";
}
warn "$ME: 'podman @subcommand {{$k' $msg\n";
}
}
}
else {
# Not documented in man. However, handle '...' as a special case
@ -237,7 +267,8 @@ sub xref_by_help {
# are way too many to list individually.
my $k_copy = $k;
while ($k_copy =~ s/\.[^.]+$//) {
if (($man->{$k_copy}||'') eq '...') {
my $parent_man = $man->{$k_copy} // '';
if (($parent_man eq '...') || ($parent_man eq '!')) {
next OPTION;
}
}
@ -397,21 +428,37 @@ sub podman_help {
}
# Special case for --format: run podman with autocomplete.
# If that lists one or more '{{.Foo}}' or '{{.Foo.' entries
# (indicating terminal or nonterminal nodes respectively)
# If that lists one or more '{{.Foo<something>' entries,
# 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).
#
# There are three possibilities for <something>:
# {{.Foo}} (end braces) | terminal node. Usual case.
# {{.Foo. (dot) | deeper struct. Hard to handle.
# {{.Foo This is a function... | function. Rare, and " " " "
#
if ($opt eq '--format') {
my @completions = _completions(@_, '--format', '{{.');
for my $c (@completions) {
if ($c =~ /^\{\{(\.\S+)(\.|\}\})$/) {
if ($c =~ /^\{\{(\.\S+)(\s+This.*\s(\d+)\s+arg.*)?$/) {
my ($fmt, $n_args) = ($1, $3 || 0);
# Strip off braces/dot, leaving just the name
$fmt =~ s/(\.|\}\})$//;
my $stripped = $1;
# First time through: convert to a hashref
$help{$opt} = {} if ! ref($help{$opt});
# Remember this param
$help{$opt}{$1} = 1;
if ($stripped eq '}}') {
$n_args = 0;
}
elsif ($stripped eq '.') {
$n_args = '...';
}
$help{$opt}{$fmt} = $n_args;
}
}
@ -644,11 +691,13 @@ 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+\|/) {
# then it's definitely a Go template. There are three cases:
# .Foo - Scalar field. The usual case.
# .Foo ... - Structure with subfields, e.g. .Foo.Xyz
# .Foo ARG(s) - Function requiring one or more arguments
#
# 1 12 3 32
if ($line =~ /^\|\s+(\.\S+)(\s+([^\|]+\S))?\s+\|/) {
my ($format, $etc) = ($1, $3);
# Confirmed: we have a table with '.Foo' strings, so
@ -660,9 +709,25 @@ sub podman_man {
}
# ...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;
# will indicate if this is a struct ("...") or a
# function.
if ($etc) {
if ($etc eq '...') { # ok
;
}
elsif ($etc =~ /^\*[a-z]+\*(\s+\*[a-z]+\*)*$/) {
# a function. Preserve only the arg COUNT, not
# their names. (command completion has no way
# to give us arg names or types).
$etc = scalar(split(' ', $etc));
}
else {
warn "$ME: $subpath:$.: unknown args '$etc' for '$format'. Valid args are '...' for nested structs or, for functions, one or more asterisk-wrapped argument names.\n";
++$Errs;
}
}
$man{$previous_flag}{$format} = $etc || 0;
# Sort order check, case-insensitive
if (lc($format) lt lc($previous_format)) {
@ -740,7 +805,7 @@ sub podman_man {
# 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;
$man{"--format"}{$_} = '!' for @$fields;
}
# Special case: the 'image trust' man page tries hard to cover both set
@ -826,7 +891,7 @@ sub podman_rst {
}
##################
# _completions # run podman __completeNoDesc, return list of completions
# _completions # run podman __complete, return list of completions
##################
sub _completions {
my $kidpid = open my $podman_fh, '-|';
@ -837,7 +902,7 @@ sub _completions {
if ($kidpid == 0) {
# We are the child
close STDERR;
exec $PODMAN, '__completeNoDesc', @_;
exec $PODMAN, '__complete', @_;
die "$ME: Could not exec: $!\n";
}
@ -860,7 +925,7 @@ sub _completions {
}
}
close $podman_fh
or warn "$ME: Error running podman __completeNoDesc @_\n";
or warn "$ME: Error running podman __complete @_\n";
return @completions;
}

View File

@ -125,8 +125,15 @@ $mclone = clone($man);
delete $mclone->{"auto-update"}{"--format"};
# --format is documented, but without a table
$mclone->{container}{list}{"--format"} = 1;
# --format is documented, with a table, but entries are wrong
$mclone->{events}{"--format"}{".Attributes"} = 0;
$mclone->{events}{"--format"}{".Image"} = '...';
$mclone->{events}{"--format"}{".Status"} = 1;
$hclone->{events}{"--format"}{".Status"} = '...';
$mclone->{events}{"--format"}{".ToHumanReadable"} = 3;
$mclone->{ps}{"--format"}{".Label"} = 0;
# --format is documented, with a table, but one entry missing
delete $mclone->{events}{"--format"}{".HealthStatus"};
delete $mclone->{events}{"--format"}{".Type"};
# -l option is not documented
delete $mclone->{pod}{inspect}{"-l"};
@ -144,10 +151,15 @@ test_xref("xref_by_help() injection", $hclone, $mclone,
[
"'podman auto-update --help' lists '--format', which is not in podman-auto-update.1.md",
"'podman container list': --format options are available through autocomplete, but are not documented in podman-ps.1.md",
"'podman events --format <TAB>' lists '.HealthStatus', which is not in podman-events.1.md",
"'podman events --format {{.Attributes' is a nested structure. Please add '...' to man page.",
"'podman events --format {{.Image' is a simple value, not a nested structure. Please remove '...' from man page.",
"'podman events --format {{.Status' is a nested structure, but the man page documents it as a function?!?",
"'podman events --format {{.ToHumanReadable' is a function that calls for 1 args; the man page lists 3. Please fix the man page.",
"'podman events --format <TAB>' lists '.Type', which is not in podman-events.1.md",
"'podman --help' lists 'new-command-in-help', which is not in podman.1.md",
"'podman partlydocumented' is not documented in man pages!",
"'podman pod inspect --help' lists '-l', which is not in podman-pod-inspect.1.md",
"'podman ps --format {{.Label' is a function that calls for 1 args. Please investigate what those are, then add them to the man page. E.g., '.Label *bool*' or '.Label *path* *bool*'",
"'podman secret --help' lists 'subcommand-in-help', which is not in podman-secret.1.md",
],
[],
@ -307,11 +319,16 @@ sed('podman-image-inspect.1.md', sub {
$line = $line . $line;
}
$line =~ s/^\|\s+\.Parent\s+\|/| .Parent BAD-ARG |/;
$line =~ s/^\|\s+\.Size\s+\|/| .Size *arg1* arg2 |/;
return $line;
},
"podman-image-inspect.1.md:NNN: format specifier '.Digest' should precede '.GraphDriver'",
"podman-image-inspect.1.md:NNN: format specifier '.ID' is a dup",
"podman-image-inspect.1.md:NNN: unknown args 'BAD-ARG' for '.Parent'. Valid args are '...' for nested structs or, for functions, one or more asterisk-wrapped argument names.",
"podman-image-inspect.1.md:NNN: unknown args '*arg1* arg2' for '.Size'. Valid args are '...' for nested structs or, for functions, one or more asterisk-wrapped argument names.",
);

View File

@ -54,13 +54,6 @@ func NewEvent(status Status) Event {
}
}
// Recycle checks if the event log has reach a limit and if so
// renames the current log and starts a new one. The remove bool
// indicates the old log file should be deleted.
func (e *Event) Recycle(path string, remove bool) error {
return errors.New("not implemented")
}
// ToJSONString returns the event as a json'ified string
func (e *Event) ToJSONString() (string, error) {
b, err := json.Marshal(e)