From ae0595c56acd9407145e406f8a58c94f7e484b54 Mon Sep 17 00:00:00 2001 From: Ed Santiago Date: Tue, 29 Jun 2021 15:56:07 -0600 Subject: [PATCH] Man page validation: part 2 of 2 This is the script that runs 'skopeo COMMAND --help' and cross-checks that all the option flags are documented in man pages, and vice-versa (all options listed in man pages appear in COMMAND's --help message). Copied from podman, with changes for skopeo-land (removing the rst checks, and conforming to skopeo conventions). Signed-off-by: Ed Santiago --- .cirrus.yml | 10 ++ Makefile | 7 +- contrib/cirrus/runner.sh | 4 + docs/skopeo-copy.1.md | 114 +++++++++--- docs/skopeo-delete.1.md | 38 +++- docs/skopeo-inspect.1.md | 12 +- docs/skopeo-list-tags.1.md | 28 ++- docs/skopeo-login.1.md | 10 +- docs/skopeo-manifest-digest.1.md | 7 +- docs/skopeo-standalone-sign.1.md | 10 +- docs/skopeo-standalone-verify.1.md | 6 + docs/skopeo-sync.1.md | 14 +- docs/skopeo.1.md | 44 +++-- hack/man-page-checker | 6 - hack/xref-helpmsgs-manpages | 277 +++++++++++++++++++++++++++++ 15 files changed, 514 insertions(+), 73 deletions(-) create mode 100755 hack/xref-helpmsgs-manpages diff --git a/.cirrus.yml b/.cirrus.yml index f00f419656..ccca6bac98 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -65,6 +65,15 @@ validate_task: make validate-local make vendor && hack/tree_status.sh +doccheck_task: + only_if: $CIRRUS_PR != '' + depends_on: + - validate + container: *build_container + script: | + "${GOSRC}/${SCRIPT_BASE}/runner.sh" setup + "${SKOPEO_PATH}/${SCRIPT_BASE}/runner.sh" build + "${SKOPEO_PATH}/${SCRIPT_BASE}/runner.sh" doccheck osx_task: only_if: ¬_docs $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' @@ -185,6 +194,7 @@ success_task: # N/B: ALL tasks must be listed here, minus their '_task' suffix. depends_on: - validate + - doccheck - osx - cross - test_skopeo diff --git a/Makefile b/Makefile index a7949fbccd..875aee329a 100644 --- a/Makefile +++ b/Makefile @@ -207,12 +207,17 @@ validate: build-container $(CONTAINER_RUN) make validate-local # This target is only intended for development, e.g. executing it from an IDE. Use (make test) for CI or pre-release testing. -test-all-local: validate-local test-unit-local +test-all-local: validate-local validate-docs test-unit-local .PHONY: validate-local validate-local: hack/make.sh validate-git-marks validate-gofmt validate-lint validate-vet + +# This invokes bin/skopeo, hence cannot be run as part of validate-local +.PHONY: validate-docs +validate-docs: hack/man-page-checker + hack/xref-helpmsgs-manpages test-unit-local: $(GPGME_ENV) $(GO) test $(MOD_VENDOR) -tags "$(BUILDTAGS)" $$($(GO) list $(MOD_VENDOR) -tags "$(BUILDTAGS)" -e ./... | grep -v '^github\.com/containers/skopeo/\(integration\|vendor/.*\)$$') diff --git a/contrib/cirrus/runner.sh b/contrib/cirrus/runner.sh index 5c76725a73..24e6d72e2f 100755 --- a/contrib/cirrus/runner.sh +++ b/contrib/cirrus/runner.sh @@ -65,6 +65,10 @@ _run_validate() { podmanmake validate-local BUILDTAGS="$BUILDTAGS" } +_run_doccheck() { + podmanmake validate-docs BUILDTAGS="$BUILDTAGS" +} + _run_unit() { podmanmake test-unit-local BUILDTAGS="$BUILDTAGS" } diff --git a/docs/skopeo-copy.1.md b/docs/skopeo-copy.1.md index 007273a9e5..99b01d3018 100644 --- a/docs/skopeo-copy.1.md +++ b/docs/skopeo-copy.1.md @@ -20,7 +20,11 @@ automatically inherit any parts of the source name. ## OPTIONS -**--all** +**--additional-tag**=_strings_ + +Additional tags (supports docker-archive). + +**--all**, **-a** If _source-image_ refers to a list of images, instead of copying just the image which matches the current OS and architecture (subject to the use of the global --override-os, --override-arch and --override-variant options), attempt to copy all of @@ -42,57 +46,119 @@ Path of the authentication file for the source registry. Uses path given by `--a Path of the authentication file for the destination registry. Uses path given by `--authfile`, if not provided. +**--dest-shared-blob-dir** _directory_ + +Directory to use to share blobs across OCI repositories. + **--digestfile** _path_ After copying the image, write the digest of the resulting image to the file. -**--format, -f** _manifest-type_ MANIFEST TYPE (oci, v2s1, or v2s2) to use in the destination (default is manifest type of source, with fallbacks) +**--encrypt-layer** _ints_ + +*Experimental* the 0-indexed layer indices, with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer) + +**--format**, **-f** _manifest-type_ + +MANIFEST TYPE (oci, v2s1, or v2s2) to use in the destination (default is manifest type of source, with fallbacks) + +**--help**, **-h** + +Print usage statement + +**--quiet**, **-q** + +Suppress output information when copying images. + +**--remove-signatures** + +Do not copy signatures, if any, from _source-image_. Necessary when copying a signed image to a destination which does not support signatures. + +**--sign-by**=_key-id_ + +Add a signature using that key ID for an image name corresponding to _destination-image_ + +**--src-shared-blob-dir** _directory_ + +Directory to use to share blobs across OCI repositories. -**--quiet, -q** suppress output information when copying images +**--encryption-key** _protocol:keyfile_ -**--remove-signatures** do not copy signatures, if any, from _source-image_. Necessary when copying a signed image to a destination which does not support signatures. +Specifies the encryption protocol, which can be JWE (RFC7516), PGP (RFC4880), and PKCS7 (RFC2315) and the key material required for image encryption. For instance, jwe:/path/to/key.pem or pgp:admin@example.com or pkcs7:/path/to/x509-file. -**--sign-by=**_key-id_ add a signature using that key ID for an image name corresponding to _destination-image_ +**--decryption-key** _key[:passphrase]_ -**--encryption-key** _protocol:keyfile_ specifies the encryption protocol, which can be JWE (RFC7516), PGP (RFC4880), and PKCS7 (RFC2315) and the key material required for image encryption. For instance, jwe:/path/to/key.pem or pgp:admin@example.com or pkcs7:/path/to/x509-file. +Key to be used for decryption of images. Key can point to keys and/or certificates. Decryption will be tried with all keys. If the key is protected by a passphrase, it is required to be passed in the argument and omitted otherwise. -**--decryption-key** _key[:passphrase]_ to be used for decryption of images. Key can point to keys and/or certificates. Decryption will be tried with all keys. If the key is protected by a passphrase, it is required to be passed in the argument and omitted otherwise. +**--src-creds** _username[:password]_ -**--src-creds** _username[:password]_ for accessing the source registry. +Credentials for accessing the source registry. -**--dest-compress** _bool-value_ Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source). +**--dest-compress** _bool-value_ -**--dest-oci-accept-uncompressed-layers** _bool-value_ Allow uncompressed image layers when saving to an OCI image using the 'oci' transport. (default is to compress things that aren't compressed). +Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source). -**--dest-creds** _username[:password]_ for accessing the destination registry. +**--dest-oci-accept-uncompressed-layers** _bool-value_ -**--src-cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the source registry or daemon. +Allow uncompressed image layers when saving to an OCI image using the 'oci' transport. (default is to compress things that aren't compressed). -**--src-no-creds** _bool-value_ Access the registry anonymously. +**--dest-creds** _username[:password]_ -**--src-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container source registry or daemon (defaults to true). +Credentials for accessing the destination registry. -**--dest-cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the destination registry or daemon. +**--src-cert-dir** _path_ -**--dest-no-creds** _bool-value_ Access the registry anonymously. +Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the source registry or daemon. -**--dest-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container destination registry or daemon (defaults to true). +**--src-no-creds** _bool-value_ -**--src-daemon-host** _host_ Copy from docker daemon at _host_. If _host_ starts with `tcp://`, HTTPS is enabled by default. To use plain HTTP, use the form `http://` (default is `unix:///var/run/docker.sock`). +Access the registry anonymously. -**--dest-daemon-host** _host_ Copy to docker daemon at _host_. If _host_ starts with `tcp://`, HTTPS is enabled by default. To use plain HTTP, use the form `http://` (default is `unix:///var/run/docker.sock`). +**--src-tls-verify** _bool-value_ + +Require HTTPS and verify certificates when talking to container source registry or daemon (defaults to true). + +**--dest-cert-dir** _path_ + +Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the destination registry or daemon. + +**--dest-no-creds** _bool-value_ + +Access the registry anonymously. + +**--dest-tls-verify** _bool-value_ + +Require HTTPS and verify certificates when talking to container destination registry or daemon (defaults to true). + +**--src-daemon-host** _host_ + +Copy from docker daemon at _host_. If _host_ starts with `tcp://`, HTTPS is enabled by default. To use plain HTTP, use the form `http://` (default is `unix:///var/run/docker.sock`). + +**--dest-daemon-host** _host_ + +Copy to docker daemon at _host_. If _host_ starts with `tcp://`, HTTPS is enabled by default. To use plain HTTP, use the form `http://` (default is `unix:///var/run/docker.sock`). Existing signatures, if any, are preserved as well. -**--dest-compress-format** _format_ Specifies the compression format to use. Supported values are: `gzip` and `zstd`. +**--dest-compress-format** _format_ + +Specifies the compression format to use. Supported values are: `gzip` and `zstd`. + +**--dest-compress-level** _format_ + +Specifies the compression level to use. The value is specific to the compression algorithm used, e.g. for zstd the accepted values are in the range 1-20 (inclusive), while for gzip it is 1-9 (inclusive). + +**--src-registry-token** _token_ + +Bearer token for accessing the source registry. -**--dest-compress-level** _format_ Specifies the compression level to use. The value is specific to the compression algorithm used, e.g. for zstd the accepted values are in the range 1-20 (inclusive), while for gzip it is 1-9 (inclusive). +**--dest-registry-token** _token_ -**--src-registry-token** _Bearer token_ for accessing the source registry. +Bearer token for accessing the destination registry. -**--dest-registry-token** _Bearer token_ for accessing the destination registry. +**--retry-times** -**--retry-times** the number of times to retry, retry wait time will be exponentially increased based on the number of failed attempts. +The number of times to retry. Retry wait time will be exponentially increased based on the number of failed attempts. ## EXAMPLES diff --git a/docs/skopeo-delete.1.md b/docs/skopeo-delete.1.md index 5406516254..b2d2259960 100644 --- a/docs/skopeo-delete.1.md +++ b/docs/skopeo-delete.1.md @@ -19,24 +19,46 @@ $ docker exec -it registry /usr/bin/registry garbage-collect /etc/docker-distrib ``` +## OPTIONS + **--authfile** _path_ - Path of the authentication file. Default is ${XDG_RUNTIME\_DIR}/containers/auth.json, which is set using `skopeo login`. - If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. +Path of the authentication file. Default is ${XDG_RUNTIME\_DIR}/containers/auth.json, which is set using `skopeo login`. +If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. + +**--creds** _username[:password]_ + +Credentials for accessing the registry. + +**--cert-dir** _path_ + +Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the registry. + +**--daemon-host** _host_ -**--creds** _username[:password]_ for accessing the registry. +Use docker daemon host at _host_ (`docker-daemon:` transport only) -**--cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the registry. +**--help**, **-h** -**--tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container registries (defaults to true). +Print usage statement -**--no-creds** _bool-value_ Access the registry anonymously. +**--no-creds** _bool-value_ + +Access the registry anonymously. Additionally, the registry must allow deletions by setting `REGISTRY_STORAGE_DELETE_ENABLED=true` for the registry daemon. -**--registry-token** _Bearer token_ for accessing the registry. +**--registry-token** _token_ + +Bearer token for accessing the registry. + +**--retry-times** + +The number of times to retry. Retry wait time will be exponentially increased based on the number of failed attempts. + +**--shared-blob-dir** _directory_ -**--retry-times** the number of times to retry, retry wait time will be exponentially increased based on the number of failed attempts. +Directory to use to share blobs across OCI repositories. ## EXAMPLES diff --git a/docs/skopeo-inspect.1.md b/docs/skopeo-inspect.1.md index 5400fb74d8..7035f0c5be 100644 --- a/docs/skopeo-inspect.1.md +++ b/docs/skopeo-inspect.1.md @@ -31,11 +31,19 @@ Output configuration in OCI format, default is to format in JSON format. Username and password for accessing the registry. +**--daemon-host** _host_ + +Use docker daemon host at _host_ (`docker-daemon:` transport only) + **--format**, **-f**=*format* Format the output using the given Go template. The keys of the returned JSON can be used as the values for the --format flag (see examples below). +**--help**, **-h** + +Print usage statement + **--no-creds** Access the registry anonymously. @@ -53,9 +61,9 @@ Registry token for accessing the registry. The number of times to retry; retry wait time will be exponentially increased based on the number of failed attempts. -**--tls-verify** +**--shared-blob-dir** _directory_ -Require HTTPS and verify certificates when talking to container registries (defaults to true). +Directory to use to share blobs across OCI repositories. ## EXAMPLES diff --git a/docs/skopeo-list-tags.1.md b/docs/skopeo-list-tags.1.md index 69371b6051..f8757da14c 100644 --- a/docs/skopeo-list-tags.1.md +++ b/docs/skopeo-list-tags.1.md @@ -10,22 +10,34 @@ Return a list of tags from _repository-name_ in a registry. _repository-name_ name of repository to retrieve tag listing from - **--authfile** _path_ +## OPTIONS - Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `skopeo login`. +**--authfile** _path_ + +Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `skopeo login`. If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. - **--creds** _username[:password]_ for accessing the registry. +**--creds** _username[:password]_ for accessing the registry. + +**--cert-dir** _path_ + +Use certificates at _path_ (\*.crt, \*.cert, \*.key) to connect to the registry. + +**--help**, **-h** + +Print usage statement + +**--no-creds** _bool-value_ - **--cert-dir** _path_ Use certificates at _path_ (\*.crt, \*.cert, \*.key) to connect to the registry. +Access the registry anonymously. - **--tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container registries (defaults to true). +**--registry-token** _Bearer token_ - **--no-creds** _bool-value_ Access the registry anonymously. +Bearer token for accessing the registry. - **--registry-token** _Bearer token_ for accessing the registry. +**--retry-times** - **--retry-times** the number of times to retry, retry wait time will be exponentially increased based on the number of failed attempts. +The number of times to retry. Retry wait time will be exponentially increased based on the number of failed attempts. ## REPOSITORY NAMES diff --git a/docs/skopeo-login.1.md b/docs/skopeo-login.1.md index 06332fcb3d..e1025b627a 100644 --- a/docs/skopeo-login.1.md +++ b/docs/skopeo-login.1.md @@ -43,16 +43,14 @@ Return the logged-in user for the registry. Return error if no login is found. Use certificates at *path* (\*.crt, \*.cert, \*.key) to connect to the registry. Default certificates directory is _/etc/containers/certs.d_. -**--tls-verify**=*true|false* - -Require HTTPS and verify certificates when contacting registries (default: true). If explicitly set to true, -then TLS verification will be used. If set to false, then TLS verification will not be used. If not specified, -TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf. - **--help**, **-h** Print usage statement +**--verbose**, **-v** + +Write more detailed information to stdout + ## EXAMPLES ``` diff --git a/docs/skopeo-manifest-digest.1.md b/docs/skopeo-manifest-digest.1.md index a246a49071..26fb0f9eb3 100644 --- a/docs/skopeo-manifest-digest.1.md +++ b/docs/skopeo-manifest-digest.1.md @@ -10,6 +10,12 @@ skopeo\-manifest\-digest - Compute a manifest digest for a manifest-file and wri Compute a manifest digest of _manifest-file_ and write it to standard output. +## OPTIONS + +**--help**, **-h** + +Print usage statement + ## EXAMPLES ```sh @@ -23,4 +29,3 @@ skopeo(1) ## AUTHORS Antonio Murdaca , Miloslav Trmac , Jhon Honce - diff --git a/docs/skopeo-standalone-sign.1.md b/docs/skopeo-standalone-sign.1.md index eae9cff429..9e887d6096 100644 --- a/docs/skopeo-standalone-sign.1.md +++ b/docs/skopeo-standalone-sign.1.md @@ -15,7 +15,15 @@ This is primarily a debugging tool, useful for special cases, and usually should _key-fingerprint_ Key identity to use for signing - **--output**|**-o** output file +## OPTIONS + +**--help**, **-h** + +Print usage statement + +**--output**, **-o** _output file_ + +Write signature to _output file_. ## EXAMPLES diff --git a/docs/skopeo-standalone-verify.1.md b/docs/skopeo-standalone-verify.1.md index fc4e44b985..626480e166 100644 --- a/docs/skopeo-standalone-verify.1.md +++ b/docs/skopeo-standalone-verify.1.md @@ -22,6 +22,12 @@ as per containers-policy.json(5). **Note:** If you do use this, make sure that the image can not be changed at the source location between the times of its verification and use. +## OPTIONS + +**--help**, **-h** + +Print usage statement + ## EXAMPLES ```sh diff --git a/docs/skopeo-sync.1.md b/docs/skopeo-sync.1.md index c19ccae571..282431f68f 100644 --- a/docs/skopeo-sync.1.md +++ b/docs/skopeo-sync.1.md @@ -32,7 +32,7 @@ When the `--scoped` option is specified, images are prefixed with the source ima name can be stored at _destination_. ## OPTIONS -**--all** +**--all**, **-a** If one of the images in __src__ refers to a list of images, instead of copying just the image which matches the current OS and architecture (subject to the use of the global --override-os, --override-arch and --override-variant options), attempt to copy all of the images in the list, and the list itself. @@ -50,17 +50,21 @@ Path of the authentication file for the source registry. Uses path given by `--a Path of the authentication file for the destination registry. Uses path given by `--authfile`, if not provided. -**--src** _transport_ Transport for the source repository. +**--src**, **-s** _transport_ Transport for the source repository. -**--dest** _transport_ Destination transport. +**--dest**, **-d** _transport_ Destination transport. -**--format, -f** _manifest-type_ Manifest Type (oci, v2s1, or v2s2) to use when syncing image(s) to a destination (default is manifest type of source, with fallbacks). +**--format**, **-f** _manifest-type_ Manifest Type (oci, v2s1, or v2s2) to use when syncing image(s) to a destination (default is manifest type of source, with fallbacks). + +**--help**, **-h** + +Print usage statement. **--scoped** Prefix images with the source image path, so that multiple images with the same name can be stored at _destination_. **--remove-signatures** Do not copy signatures, if any, from _source-image_. This is necessary when copying a signed image to a destination which does not support signatures. -**--sign-by=**_key-id_ Add a signature using that key ID for an image name corresponding to _destination-image_. +**--sign-by**=_key-id_ Add a signature using that key ID for an image name corresponding to _destination-image_. **--src-creds** _username[:password]_ for accessing the source registry. diff --git a/docs/skopeo.1.md b/docs/skopeo.1.md index ad980249ea..b067d0d531 100644 --- a/docs/skopeo.1.md +++ b/docs/skopeo.1.md @@ -51,27 +51,49 @@ See [containers-transports(5)](https://github.com/containers/image/blob/master/d ## OPTIONS - **--command-timeout** _duration_ Timeout for the command execution. +**--command-timeout** _duration_ - **--debug** enable debug output +Timeout for the command execution. - **--help**|**-h** Show help +**--debug** - **--insecure-policy** Adopt an insecure, permissive policy that allows anything. This obviates the need for a policy file. +enable debug output - **--override-arch** _arch_ Use _arch_ instead of the architecture of the machine for choosing images. +**--help**, **-h** - **--override-os** _OS_ Use _OS_ instead of the running OS for choosing images. +Show help - **--override-variant** _VARIANT_ Use _VARIANT_ instead of the running architecture variant for choosing images. +**--insecure-policy** - **--policy** _path-to-policy_ Path to a policy.json file to use for verifying signatures and deciding whether an image is trusted, overriding the default trust policy file. +Adopt an insecure, permissive policy that allows anything. This obviates the need for a policy file. - **--registries.d** _dir_ use registry configuration files in _dir_ (e.g. for container signature storage), overriding the default path. +**--override-arch** _arch_ - **--tmpdir** _dir_ used to store temporary files. Defaults to /var/tmp. +Use _arch_ instead of the architecture of the machine for choosing images. - **--version**|**-v** print the version number +**--override-os** _os_ + +Use _OS_ instead of the running OS for choosing images. + +**--override-variant** _variant_ + +Use _variant_ instead of the running architecture variant for choosing images. + +**--policy** _path-to-policy_ + +Path to a policy.json file to use for verifying signatures and deciding whether an image is trusted, overriding the default trust policy file. + +**--registries.d** _dir_ + +Use registry configuration files in _dir_ (e.g. for container signature storage), overriding the default path. + +**--tmpdir** _dir_ + +Directory used to store temporary files. Defaults to /var/tmp. + +**--version**, **-v** + +Print the version number ## COMMANDS diff --git a/hack/man-page-checker b/hack/man-page-checker index a340c9294e..e8993761be 100755 --- a/hack/man-page-checker +++ b/hack/man-page-checker @@ -6,9 +6,6 @@ # script that cross-checks that each option in skopeo foo --help is listed # in skopeo-foo.1.md and vice-versa; that one is xref-helpmsgs-manpages. # -# IMPORTANT NOTE: this script runs on Macs, on which sed is an ancient -# non-gnu version. To make sed work on all platforms, we invoke with '-E'. -# verbose= for i; do @@ -70,9 +67,6 @@ function compare_usage() { local cmd="$1" local from_man="$2" - # Sometimes in CI we run before skopeo gets built. - test -x ../bin/skopeo || return - # Run 'cmd --help', grab the line immediately after 'Usage:' local help_output=$(../bin/$cmd --help) local from_help=$(echo "$help_output" | grep -A1 '^Usage:' | tail -1) diff --git a/hack/xref-helpmsgs-manpages b/hack/xref-helpmsgs-manpages new file mode 100755 index 0000000000..43354184b3 --- /dev/null +++ b/hack/xref-helpmsgs-manpages @@ -0,0 +1,277 @@ +#!/usr/bin/perl +# +# xref-helpmsgs-manpages - cross-reference --help options against man pages +# +package LibPod::CI::XrefHelpmsgsManpages; + +use v5.14; +use utf8; + +use strict; +use warnings; + +(our $ME = $0) =~ s|.*/||; +our $VERSION = '0.1'; + +# For debugging, show data structures using DumpTree($var) +#use Data::TreeDumper; $Data::TreeDumper::Displayaddress = 0; + +# unbuffer output +$| = 1; + +############################################################################### +# BEGIN user-customizable section + +# Path to skopeo executable +my $Default_Skopeo = './bin/skopeo'; +my $SKOPEO = $ENV{SKOPEO} || $Default_Skopeo; + +# Path to all doc files (markdown) +my $Docs_Path = 'docs'; + +# Global error count +my $Errs = 0; + +# END user-customizable section +############################################################################### + +############################################################################### +# BEGIN boilerplate args checking, usage messages + +sub usage { + print <<"END_USAGE"; +Usage: $ME [OPTIONS] + +$ME recursively runs 'skopeo --help' against +all subcommands; and recursively reads skopeo-*.1.md files +in $Docs_Path, then cross-references that each --help +option is listed in the appropriate man page and vice-versa. + +$ME invokes '\$SKOPEO' (default: $Default_Skopeo). + +Exit status is zero if no inconsistencies found, one otherwise + +OPTIONS: + + -v, --verbose show verbose progress indicators + -n, --dry-run make no actual changes + + --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; +our $verbose = 0; +sub handle_opts { + use Getopt::Long; + GetOptions( + 'debug!' => \$debug, + 'verbose|v' => \$verbose, + + help => \&usage, + 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 + + # Fetch command-line arguments. Barf if too many. + die "$ME: Too many arguments; try $ME --help\n" if @ARGV; + + my $help = skopeo_help(); + my $man = skopeo_man('skopeo'); + + xref_by_help($help, $man); + xref_by_man($help, $man); + + exit !!$Errs; +} + +############################################################################### +# BEGIN cross-referencing + +################## +# xref_by_help # Find keys in '--help' but not in man +################## +sub xref_by_help { + my ($help, $man, @subcommand) = @_; + + for my $k (sort keys %$help) { + if (exists $man->{$k}) { + if (ref $help->{$k}) { + xref_by_help($help->{$k}, $man->{$k}, @subcommand, $k); + } + # Otherwise, non-ref is leaf node such as a --option + } + else { + my $man = $man->{_path} || 'man'; + warn "$ME: skopeo @subcommand --help lists $k, but $k not in $man\n"; + ++$Errs; + } + } +} + +################# +# xref_by_man # Find keys in man pages but not in --help +################# +# +# In an ideal world we could share the functionality in one function; but +# there are just too many special cases in man pages. +# +sub xref_by_man { + my ($help, $man, @subcommand) = @_; + + # FIXME: this generates way too much output + for my $k (grep { $_ ne '_path' } sort keys %$man) { + if (exists $help->{$k}) { + if (ref $man->{$k}) { + xref_by_man($help->{$k}, $man->{$k}, @subcommand, $k); + } + } + elsif ($k ne '--help' && $k ne '-h') { + my $man = $man->{_path} || 'man'; + + warn "$ME: skopeo @subcommand: $k in $man, but not --help\n"; + ++$Errs; + } + } +} + +# END cross-referencing +############################################################################### +# BEGIN data gathering + +################# +# skopeo_help # Parse output of 'skopeo [subcommand] --help' +################# +sub skopeo_help { + my %help; + open my $fh, '-|', $SKOPEO, @_, '--help' + or die "$ME: Cannot fork: $!\n"; + my $section = ''; + while (my $line = <$fh>) { + # Cobra is blessedly consistent in its output: + # Usage: ... + # Available Commands: + # .... + # Options: + # .... + # + # Start by identifying the section we're in... + if ($line =~ /^Available\s+(Commands):/) { + $section = lc $1; + } + elsif ($line =~ /^(Flags):/) { + $section = lc $1; + } + + # ...then track commands and options. For subcommands, recurse. + elsif ($section eq 'commands') { + if ($line =~ /^\s{1,4}(\S+)\s/) { + my $subcommand = $1; + print "> skopeo @_ $subcommand\n" if $debug; + $help{$subcommand} = skopeo_help(@_, $subcommand) + unless $subcommand eq 'help'; # 'help' not in man + } + } + elsif ($section eq 'flags') { + # Handle '--foo' or '-f, --foo' + if ($line =~ /^\s{1,10}(--\S+)\s/) { + print "> skopeo @_ $1\n" if $debug; + $help{$1} = 1; + } + elsif ($line =~ /^\s{1,10}(-\S),\s+(--\S+)\s/) { + print "> skopeo @_ $1, $2\n" if $debug; + $help{$1} = $help{$2} = 1; + } + } + } + close $fh + or die "$ME: Error running 'skopeo @_ --help'\n"; + + return \%help; +} + + +################ +# skopeo_man # Parse contents of skopeo-*.1.md +################ +sub skopeo_man { + my $command = shift; + my $manpath = "$Docs_Path/$command.1.md"; + print "** $manpath \n" if $debug; + + my %man = (_path => $manpath); + open my $fh, '<', $manpath + or die "$ME: Cannot read $manpath: $!\n"; + my $section = ''; + my @most_recent_flags; + my $previous_subcmd = ''; + while (my $line = <$fh>) { + chomp $line; + next unless $line; # skip empty lines + + # .md files designate sections with leading double hash + if ($line =~ /^##\s*OPTIONS/) { + $section = 'flags'; + } + elsif ($line =~ /^\#\#\s+(SUB)?COMMANDS/) { + $section = 'commands'; + } + elsif ($line =~ /^\#\#[^#]/) { + $section = ''; + } + + # This will be a table containing subcommand names, links to man pages. + elsif ($section eq 'commands') { + # In skopeo.1.md + if ($line =~ /^\|\s*\[skopeo-(\S+?)\(\d\)\]/) { + # $1 will be changed by recursion _*BEFORE*_ left-hand assignment + my $subcmd = $1; + $man{$subcmd} = skopeo_man("skopeo-$1"); + } + } + + # Options should always be of the form '**-f**' or '**\-\-flag**', + # possibly separated by comma-space. + elsif ($section eq 'flags') { + # If option has long and short form, long must come first. + # This is a while-loop because there may be multiple long + # option names (not in skopeo ATM, but leave the possibility open) + while ($line =~ s/^\*\*(--[a-z0-9.-]+)\*\*(=\*[a-zA-Z0-9-]+\*)?(,\s+)?//g) { + $man{$1} = 1; + } + # Short form + if ($line =~ s/^\*\*(-[a-zA-Z0-9.])\*\*(=\*[a-zA-Z0-9-]+\*)?//g) { + $man{$1} = 1; + } + } + } + close $fh; + + return \%man; +} + + + +# END data gathering +############################################################################### + +1;