Merge pull request #21233 from edsantiago/quadlet-docs-xref

Quadlet: ensure all keys are documented
This commit is contained in:
openshift-merge-bot[bot]
2024-01-18 18:45:29 +00:00
committed by GitHub
4 changed files with 330 additions and 76 deletions

View File

@ -544,6 +544,7 @@ man-page-check: bin/podman
hack/man-page-checker
hack/xref-helpmsgs-manpages
hack/man-page-table-check
hack/xref-quadlet-docs
.PHONY: swagger-check
swagger-check:

View File

@ -162,18 +162,18 @@ Valid options for `[Container]` are listed below:
| ContainerName=name | --name name |
| ContainersConfModule=/etc/nvd\.conf | --module=/etc/nvd\.conf |
| DNS=192.168.55.1 | --dns=192.168.55.1 |
| DNSSearch=foo.com | --dns-search=foo.com |
| DNSOption=ndots:1 | --dns-option=ndots:1 |
| DNSSearch=foo.com | --dns-search=foo.com |
| DropCapability=CAP | --cap-drop=CAP |
| Entrypoint=/foo.sh | --entrypoint=/foo.sh |
| Environment=foo=bar | --env foo=bar |
| EnvironmentFile=/tmp/env | --env-file /tmp/env |
| EnvironmentHost=true | --env-host |
| Entrypoint=/foo.sh | --entrypoint=/foo.sh |
| Exec=/usr/bin/command | Command after image specification - /usr/bin/command |
| ExposeHostPort=50-59 | --expose 50-59 |
| GIDMap=0:10000:10 | --gidmap=0:10000:10 |
| Group=1234 | --user UID:1234 |
| GlobalArgs=--log-level=debug | --log-level=debug |
| Group=1234 | --user UID:1234 |
| HealthCmd=/usr/bin/command | --health-cmd=/usr/bin/command |
| HealthInterval=2m | --health-interval=2m |
| HealthOnFailure=kill | --health-on-failure=kill |
@ -195,7 +195,6 @@ Valid options for `[Container]` are listed below:
| Mount=type=... | --mount type=... |
| Network=host | --net host |
| NoNewPrivileges=true | --security-opt no-new-privileges |
| Rootfs=/var/lib/rootfs | --rootfs /var/lib/rootfs |
| Notify=true | --sdnotify container |
| PidsLimit=10000 | --pids-limit 10000 |
| Pod=pod-name | --pod=pod-name |
@ -204,8 +203,10 @@ Valid options for `[Container]` are listed below:
| Pull=never | --pull=never |
| ReadOnly=true | --read-only |
| ReadOnlyTmpfs=true | --read-only-tmpfs |
| Rootfs=/var/lib/rootfs | --rootfs /var/lib/rootfs |
| RunInit=true | --init |
| SeccompProfile=/tmp/s.json | --security-opt seccomp=/tmp/s.json |
| Secret=secret | --secret=secret[,opt=opt ...] |
| SecurityLabelDisable=true | --security-opt label=disable |
| SecurityLabelFileType=usr_t | --security-opt label=filetype:usr_t |
| SecurityLabelLevel=s0:c1,c2 | --security-opt label=level:s0:c1,c2 |
@ -306,6 +307,12 @@ For example:
DropCapability=CAP_DAC_OVERRIDE CAP_IPC_OWNER
```
### `Entrypoint=`
Override the default ENTRYPOINT from the image.
Equivalent to the Podman `--entrypoint` option.
Specify multi option commands in the form of a json string.
### `Environment=`
Set an environment variable in the container. This uses the same format as
@ -322,12 +329,6 @@ This key may be used multiple times, and the order persists when passed to `podm
Use the host environment inside of the container.
#### `Entrypoint=`
Override the default ENTRYPOINT from the image.
Equivalent to the Podman `--entrypoint` option.
Specify multi option commands in the form of a json string.
### `Exec=`
If this is set then it defines what command line to run in the container. If it is not set the
@ -499,14 +500,6 @@ This key can be listed multiple times.
If enabled, this disables the container processes from gaining additional privileges via things like
setuid and file capabilities.
### `Rootfs=`
The rootfs to use for the container. Rootfs points to a directory on the system that contains the content to be run within the container. This option conflicts with the `Image` option.
The format of the rootfs is the same as when passed to `podman run --rootfs`, so it supports overlay mounts as well.
Note: On SELinux systems, the rootfs needs the correct label, which is by default unconfined_u:object_r:container_file_t:s0.
### `Notify=` (defaults to `no`)
By default, Podman is run in such a way that the systemd startup notify command is handled by
@ -576,6 +569,14 @@ If enabled, makes the image read-only.
If ReadOnly is set to `yes`, mount a read-write tmpfs on /dev, /dev/shm, /run, /tmp, and /var/tmp.
### `Rootfs=`
The rootfs to use for the container. Rootfs points to a directory on the system that contains the content to be run within the container. This option conflicts with the `Image` option.
The format of the rootfs is the same as when passed to `podman run --rootfs`, so it supports overlay mounts as well.
Note: On SELinux systems, the rootfs needs the correct label, which is by default unconfined_u:object_r:container_file_t:s0.
### `RunInit=` (default to `no`)
If enabled, the container has a minimal init process inside the
@ -586,6 +587,11 @@ container that forwards signals and reaps processes.
Set the seccomp profile to use in the container. If unset, the default podman profile is used.
Set to either the pathname of a json file, or `unconfined` to disable the seccomp filters.
### `Secret=`
Use a Podman secret in the container either as a file or an environment variable.
This is equivalent to the Podman `--secret` option and generally has the form `secret[,opt=opt ...]`
### `SecurityLabelDisable=`
Turn off label separation for the container.
@ -606,11 +612,6 @@ Allow SecurityLabels to function within the container. This allows separation of
Set the label process type for the container processes.
### `Secret=`
Use a Podman secret in the container either as a file or an environment variable.
This is equivalent to the Podman `--secret` option and generally has the form `secret[,opt=opt ...]`
### `ShmSize=`
Size of /dev/shm.
@ -646,6 +647,10 @@ For example:
Sysctl=net.ipv6.conf.all.disable_ipv6=1 net.ipv6.conf.all.use_tempaddr=1
```
### `Timezone=` (if unset uses system-configured default)
The timezone to run the container in.
### `Tmpfs=`
Mount a tmpfs in the container. This is equivalent to the Podman `--tmpfs` option, and
@ -653,10 +658,6 @@ generally has the form `CONTAINER-DIR[:OPTIONS]`.
This key can be listed multiple times.
### `Timezone=` (if unset uses system-configured default)
The timezone to run the container in.
### `UIDMap=`
Run the container in a new user namespace using the supplied UID mapping.
@ -835,6 +836,7 @@ Valid options for `[Kube]` are listed below:
| AutoUpdate=registry | --annotation "io.containers.autoupdate=registry" |
| ConfigMap=/tmp/config.map | --config-map /tmp/config.map |
| ContainersConfModule=/etc/nvd\.conf | --module=/etc/nvd\.conf |
| ExitCodePropagation=how | How to propagate container error status |
| GlobalArgs=--log-level=debug | --log-level=debug |
| KubeDownForce=true | --force (for `podman kube down`) |
| LogDriver=journald | --log-driver journald |
@ -1145,6 +1147,8 @@ Valid options for `[Volume]` are listed below:
| Label="foo=bar" | --label "foo=bar" |
| Options=XYZ | --opt XYZ |
| PodmanArgs=--driver=image | --driver=image |
| Type=type | Filesystem type of Device |
| User=123 | --opt uid=123 |
| VolumeName=foo | podman volume create foo |
Supported keys in `[Volume]` section are:

251
hack/xref-quadlet-docs Executable file
View File

@ -0,0 +1,251 @@
#!/usr/bin/perl
#
# xref-quadlet-docs - cross-validate quadlet man page vs actual source
#
# $Id: .perl-template,v 1.2 2020/03/03 20:08:31 esm Exp esm $
#
package Podman::CrossrefQuadletDocs;
use v5.14;
use utf8;
use strict;
use warnings;
(our $ME = $0) =~ s|.*/||;
our $VERSION = '0.1';
###############################################################################
# BEGIN user-customizable section
our $Go = 'pkg/systemd/quadlet/quadlet.go';
our $Doc = 'docs/source/markdown/podman-systemd.unit.5.md';
# END user-customizable section
###############################################################################
###############################################################################
# BEGIN boilerplate args checking, usage messages
sub usage {
print <<"END_USAGE";
Usage: $ME [OPTIONS]
$ME cross-checks quadlet documentation between the Go source[Go]
and the man page[MD].
[Go]: $Go
[MD]: $Doc
We check that:
* all keys in [Go] are documented in [MD]
* all keys in [MD] exist in [Go]
* any keys listed in [MD] tables also have a description block
and vice-versa
* all keys everywhere are in sorted order
OPTIONS:
--help display this message
--version display program name and version
END_USAGE
exit;
}
# Command-line options. Note that this operates directly on @ARGV !
our $debug = 0;
sub handle_opts {
use Getopt::Long;
GetOptions(
'debug!' => \$debug,
help => \&usage,
man => \&man,
version => sub { print "$ME version $VERSION\n"; exit 0 },
) or die "Try `$ME --help' for help\n";
}
# END boilerplate args checking, usage messages
###############################################################################
############################## CODE BEGINS HERE ###############################
# The term is "modulino".
__PACKAGE__->main() unless caller();
# Main code.
sub main {
# Note that we operate directly on @ARGV, not on function parameters.
# This is deliberate: it's because Getopt::Long only operates on @ARGV
# and there's no clean way to make it use @_.
handle_opts(); # will set package globals
# No command-line args
die "$ME: Too many arguments; try $ME --help\n" if @ARGV;
my $errs = 0;
$SIG{__WARN__} = sub {
print STDERR "@_";
++$errs;
};
# Assume that Go source file has Truth
my $true_keys = read_go($Go);
# Read md file, compare against Truth
crossref_doc($Doc, $true_keys);
exit $errs;
}
#############
# read_go # Returns list of key strings found in quadlet.go
#############
sub read_go {
my $path = shift;
open my $fh, '<', $path
or die "$ME: Cannot read $path: $!\n";;
my @found; # List of key strings
my $last_constname; # Most recently seen const name
while (my $line = <$fh>) {
# Only interested in lines of the form KeyFoo = "Foo"
if ($line =~ /^\s+Key(\S+)\s+=\s+"(\S+)"/) {
my ($constname, $keystring) = ($1, $2);
my $deprecated = ($line =~ m!\s//\s+deprecated!i);
# const name must be the same as the string
$constname eq $keystring
or warn "$ME: $path:$.: mismatched strings: Key$constname = \"$keystring\"\n";
# Sorting check.
if ($last_constname) {
if (lc($constname) lt lc($last_constname)) {
warn "$ME: $path:$.: out-of-order variable name 'Key$constname' should precede 'Key$last_constname'\n";
}
}
$last_constname = $constname;
push @found, $keystring
unless $deprecated;
}
}
close $fh;
\@found;
}
##################
# crossref_doc # Read the markdown page, cross-check against Truth
##################
sub crossref_doc {
my $path = shift; # in: path to .md file
my $true_keys = shift; # in: AREF, list of keys from .go
open my $fh, '<', $path
or die "$ME: Cannot read $path: $!\n";;
my $unit = '';
my %documented;
my @found_in_table;
my @described;
# Helper function: when done reading description blocks,
# make sure that there's one block for each key listed
# in the table. Defined as a local function because we
# need to call it from two different places.
my $crossref_against_table = sub {
for my $k (@found_in_table) {
grep { $_ eq $k } @described
or warn "$ME: key not documented: '$k' listed in table for unit '$unit' but not actually documented\n";
}
};
# Main loop: read the docs line by line
while (my $line = <$fh>) {
chomp $line;
# New section, with its own '| table |' and '### Keyword blocks'
if ($line =~ /^##\s+(\S+)\s+units\s+\[(\S+)\]/) {
my $new_unit = $1;
$new_unit eq $2
or warn "$ME: $path:$.: inconsistent block names in '$line'\n";
$crossref_against_table->();
$unit = $new_unit;
# Reset, because each section has its own table & blocks
@found_in_table = ();
@described = ();
next;
}
# Table line
if ($line =~ s/^\|\s+//) {
next if $line =~ /^\*\*/; # title
next if $line =~ /^-----/; # divider
if ($line =~ /^([A-Z][A-Za-z6]+)=/) {
my $key = $1;
grep { $_ eq $key } @$true_keys
or warn "$ME: $path:$.: unknown key '$key' (not present in $Go)\n";
# Sorting check
if (@found_in_table) {
if (lc($key) lt lc($found_in_table[-1])) {
warn "$ME: $path:$.: out-of-order key '$key' in table\n";
}
}
push @found_in_table, $key;
$documented{$key}++;
}
else {
warn "$ME: $path:$.: cannot grok table line '$line'\n";
}
}
# Description block
elsif ($line =~ /^###\s+`(\S+)=`/) {
my $key = $1;
# Check for dups and for out-of-order
if (@described) {
if (lc($key) lt lc($described[-1])) {
warn "$ME: $path:$.: out-of-order key '$key'\n";
}
if (grep { lc($_) eq lc($key) } @described) {
warn "$ME: $path:$.: duplicate key '$key'\n";
}
}
grep { $_ eq $key } @found_in_table
or warn "$ME: $path:$.: key '$key' is not listed in table for unit '$unit'\n";
push @described, $key;
$documented{$key}++;
}
}
close $fh;
# Final cross-check between table and description blocks
$crossref_against_table->();
# Check that no Go keys are missing
(my $md_basename = $path) =~ s|^.*/||;
for my $k (@$true_keys) {
$documented{$k}
or warn "$ME: undocumented key: '$k' not found anywhere in $md_basename\n";
}
}
1;

View File

@ -57,25 +57,27 @@ const (
KeyAuthFile = "AuthFile"
KeyAutoUpdate = "AutoUpdate"
KeyCertDir = "CertDir"
KeyCreds = "Creds"
KeyDecryptionKey = "DecryptionKey"
KeyConfigMap = "ConfigMap"
KeyContainerName = "ContainerName"
KeyContainersConfModule = "ContainersConfModule"
KeyCopy = "Copy"
KeyCreds = "Creds"
KeyDecryptionKey = "DecryptionKey"
KeyDevice = "Device"
KeyDisableDNS = "DisableDNS"
KeyDNS = "DNS"
KeyDNSOption = "DNSOption"
KeyDNSSearch = "DNSSearch"
KeyDriver = "Driver"
KeyDropCapability = "DropCapability"
KeyEntrypoint = "Entrypoint"
KeyEnvironment = "Environment"
KeyEnvironmentFile = "EnvironmentFile"
KeyEnvironmentHost = "EnvironmentHost"
KeyEntrypoint = "Entrypoint"
KeyExec = "Exec"
KeyExitCodePropagation = "ExitCodePropagation"
KeyExposeHostPort = "ExposeHostPort"
KeyGateway = "Gateway"
KeyGIDMap = "GIDMap"
KeyGlobalArgs = "GlobalArgs"
KeyGroup = "Group"
@ -91,42 +93,37 @@ const (
KeyHealthStartupTimeout = "HealthStartupTimeout"
KeyHealthTimeout = "HealthTimeout"
KeyHostName = "HostName"
KeyIP = "IP"
KeyIP6 = "IP6"
KeyImage = "Image"
KeyImageTag = "ImageTag"
KeyInternal = "Internal"
KeyIP = "IP"
KeyIP6 = "IP6"
KeyIPAMDriver = "IPAMDriver"
KeyIPRange = "IPRange"
KeyIPv6 = "IPv6"
KeyKubeDownForce = "KubeDownForce"
KeyLabel = "Label"
KeyLogDriver = "LogDriver"
KeyMask = "Mask"
KeyMount = "Mount"
KeyNetwork = "Network"
KeyNetworkDisableDNS = "DisableDNS"
KeyNetworkDriver = "Driver"
KeyNetworkGateway = "Gateway"
KeyNetworkIPAMDriver = "IPAMDriver"
KeyNetworkIPRange = "IPRange"
KeyNetworkIPv6 = "IPv6"
KeyNetworkInternal = "Internal"
KeyNetworkName = "NetworkName"
KeyNetworkOptions = "Options"
KeyNetworkSubnet = "Subnet"
KeyNoNewPrivileges = "NoNewPrivileges"
KeyNotify = "Notify"
KeyOptions = "Options"
KeyOS = "OS"
KeyPidsLimit = "PidsLimit"
KeyPod = "Pod"
KeyPodmanArgs = "PodmanArgs"
KeyPodName = "PodName"
KeyPod = "Pod"
KeyPublishPort = "PublishPort"
KeyPull = "Pull"
KeyReadOnly = "ReadOnly"
KeyReadOnlyTmpfs = "ReadOnlyTmpfs"
KeyRemapGID = "RemapGid"
KeyRemapUID = "RemapUid"
KeyRemapUIDSize = "RemapUidSize"
KeyRemapUsers = "RemapUsers"
KeyRemapGid = "RemapGid" //nolint:stylecheck // deprecated
KeyRemapUid = "RemapUid" //nolint:stylecheck // deprecated
KeyRemapUidSize = "RemapUidSize" //nolint:stylecheck // deprecated
KeyRemapUsers = "RemapUsers" // deprecated
KeyRootfs = "Rootfs"
KeyRunInit = "RunInit"
KeySeccompProfile = "SeccompProfile"
@ -140,6 +137,7 @@ const (
KeyShmSize = "ShmSize"
KeyStopTimeout = "StopTimeout"
KeySubGIDMap = "SubGIDMap"
KeySubnet = "Subnet"
KeySubUIDMap = "SubUIDMap"
KeySysctl = "Sysctl"
KeyTimezone = "Timezone"
@ -152,7 +150,7 @@ const (
KeyUser = "User"
KeyUserNS = "UserNS"
KeyVariant = "Variant"
KeyVolatileTmp = "VolatileTmp"
KeyVolatileTmp = "VolatileTmp" // deprecated
KeyVolume = "Volume"
KeyVolumeName = "VolumeName"
KeyWorkingDir = "WorkingDir"
@ -217,9 +215,9 @@ var (
KeyPull: true,
KeyReadOnly: true,
KeyReadOnlyTmpfs: true,
KeyRemapGID: true,
KeyRemapUID: true,
KeyRemapUIDSize: true,
KeyRemapGid: true,
KeyRemapUid: true,
KeyRemapUidSize: true,
KeyRemapUsers: true,
KeyRootfs: true,
KeyRunInit: true,
@ -270,16 +268,16 @@ var (
KeyDNS: true,
KeyContainersConfModule: true,
KeyGlobalArgs: true,
KeyNetworkDisableDNS: true,
KeyNetworkDriver: true,
KeyNetworkGateway: true,
KeyNetworkIPAMDriver: true,
KeyNetworkIPRange: true,
KeyNetworkIPv6: true,
KeyNetworkInternal: true,
KeyDisableDNS: true,
KeyDriver: true,
KeyGateway: true,
KeyIPAMDriver: true,
KeyIPRange: true,
KeyIPv6: true,
KeyInternal: true,
KeyNetworkName: true,
KeyNetworkOptions: true,
KeyNetworkSubnet: true,
KeyOptions: true,
KeySubnet: true,
KeyPodmanArgs: true,
}
@ -295,9 +293,9 @@ var (
KeyNetwork: true,
KeyPodmanArgs: true,
KeyPublishPort: true,
KeyRemapGID: true,
KeyRemapUID: true,
KeyRemapUIDSize: true,
KeyRemapGid: true,
KeyRemapUid: true,
KeyRemapUidSize: true,
KeyRemapUsers: true,
KeySetWorkingDirectory: true,
KeyUserNS: true,
@ -826,7 +824,7 @@ func ConvertNetwork(network *parser.UnitFile, name string) (*parser.UnitFile, st
podman.add("network", "create", "--ignore")
if disableDNS := network.LookupBooleanWithDefault(NetworkGroup, KeyNetworkDisableDNS, false); disableDNS {
if disableDNS := network.LookupBooleanWithDefault(NetworkGroup, KeyDisableDNS, false); disableDNS {
podman.add("--disable-dns")
}
@ -835,14 +833,14 @@ func ConvertNetwork(network *parser.UnitFile, name string) (*parser.UnitFile, st
podman.addf("--dns=%s", ipAddr)
}
driver, ok := network.Lookup(NetworkGroup, KeyNetworkDriver)
driver, ok := network.Lookup(NetworkGroup, KeyDriver)
if ok && len(driver) > 0 {
podman.addf("--driver=%s", driver)
}
subnets := network.LookupAll(NetworkGroup, KeyNetworkSubnet)
gateways := network.LookupAll(NetworkGroup, KeyNetworkGateway)
ipRanges := network.LookupAll(NetworkGroup, KeyNetworkIPRange)
subnets := network.LookupAll(NetworkGroup, KeySubnet)
gateways := network.LookupAll(NetworkGroup, KeyGateway)
ipRanges := network.LookupAll(NetworkGroup, KeyIPRange)
if len(subnets) > 0 {
if len(gateways) > len(subnets) {
return nil, "", fmt.Errorf("cannot set more gateways than subnets")
@ -863,19 +861,19 @@ func ConvertNetwork(network *parser.UnitFile, name string) (*parser.UnitFile, st
return nil, "", fmt.Errorf("cannot set gateway or range without subnet")
}
if internal := network.LookupBooleanWithDefault(NetworkGroup, KeyNetworkInternal, false); internal {
if internal := network.LookupBooleanWithDefault(NetworkGroup, KeyInternal, false); internal {
podman.add("--internal")
}
if ipamDriver, ok := network.Lookup(NetworkGroup, KeyNetworkIPAMDriver); ok && len(ipamDriver) > 0 {
if ipamDriver, ok := network.Lookup(NetworkGroup, KeyIPAMDriver); ok && len(ipamDriver) > 0 {
podman.addf("--ipam-driver=%s", ipamDriver)
}
if ipv6 := network.LookupBooleanWithDefault(NetworkGroup, KeyNetworkIPv6, false); ipv6 {
if ipv6 := network.LookupBooleanWithDefault(NetworkGroup, KeyIPv6, false); ipv6 {
podman.add("--ipv6")
}
networkOptions := network.LookupAllKeyVal(NetworkGroup, KeyNetworkOptions)
networkOptions := network.LookupAllKeyVal(NetworkGroup, KeyOptions)
if len(networkOptions) > 0 {
podman.addKeys("--opt", networkOptions)
}
@ -1390,8 +1388,8 @@ func handleUserMappings(unitFile *parser.UnitFile, groupName string, podman *Pod
}
if mappingsDefined {
_, hasRemapUID := unitFile.Lookup(groupName, KeyRemapUID)
_, hasRemapGID := unitFile.Lookup(groupName, KeyRemapGID)
_, hasRemapUID := unitFile.Lookup(groupName, KeyRemapUid)
_, hasRemapGID := unitFile.Lookup(groupName, KeyRemapGid)
_, RemapUsers := unitFile.LookupLast(groupName, KeyRemapUsers)
if hasRemapUID || hasRemapGID || RemapUsers {
return fmt.Errorf("deprecated Remap keys are set along with explicit mapping keys")
@ -1403,8 +1401,8 @@ func handleUserMappings(unitFile *parser.UnitFile, groupName string, podman *Pod
}
func handleUserRemap(unitFile *parser.UnitFile, groupName string, podman *PodmanCmdline, isUser, supportManual bool) error {
uidMaps := unitFile.LookupAllStrv(groupName, KeyRemapUID)
gidMaps := unitFile.LookupAllStrv(groupName, KeyRemapGID)
uidMaps := unitFile.LookupAllStrv(groupName, KeyRemapUid)
gidMaps := unitFile.LookupAllStrv(groupName, KeyRemapGid)
remapUsers, _ := unitFile.LookupLast(groupName, KeyRemapUsers)
switch remapUsers {
case "":
@ -1433,7 +1431,7 @@ func handleUserRemap(unitFile *parser.UnitFile, groupName string, podman *Podman
for _, gidMap := range gidMaps {
autoOpts = append(autoOpts, "gidmapping="+gidMap)
}
uidSize := unitFile.LookupUint32(groupName, KeyRemapUIDSize, 0)
uidSize := unitFile.LookupUint32(groupName, KeyRemapUidSize, 0)
if uidSize > 0 {
autoOpts = append(autoOpts, fmt.Sprintf("size=%v", uidSize))
}