NAME

App::FuguVM::CLI - the subcommands of fuguvm

SYNOPSIS

use App::FuguVM::CLI;

exit App::FuguVM::CLI->run(@ARGV);

DESCRIPTION

The module holds the subcommand bodies of fuguvm(1). The dispatch, the option parsing, the usage and the generic exit codes come from Fugu::CLI.

Each subcommand declares its own options in one table beside its body, so no command repeats a Getopt::Long block. _prepare applies the global options and loads the project before a body runs, and init is the one command that runs without a project. The five lifecycle commands have no body of their own: their table entry names the App::FuguVM::Guest verb, and run calls it on the loaded VM.

Command data goes to standard output; a diagnostic goes to the logger, which writes to standard error. Thus fuguvm snapshot list --names stays readable in a pipeline when the tool has something to warn about.

THE STATUS REPORT

fuguvm status writes one key: value line for each key to standard output, in sorted key order. It writes the lines whatever --quiet says, because the lines are data. The key set is stable: every key appears on every run. A value can be empty, and an empty value means "not now". A line with an empty value ends with the colon and one space, so every line has one shape.

The keys are accel, arch, bind_address, console_port, disk_exists, installed, name, pid, proxy_url, ssh_port and state. pid and the two ports of an auto directive are empty for a stopped guest, and proxy_url is empty while the proxy does not run.

fuguvm status <key> writes the bare value of one key, with no key name and no colon. Thus a make target reads one port without a text filter. An unknown key gives exit code 2, and the diagnostic names every valid key.

fuguvm disk info shares the printer, so its report follows the same key: value contract on standard output.

THE FOUR CONNECTION VERBS

ssh, put, get and console each need a running guest. Each one checks first, and a guest that does not run gives exit code 1 with a message that names the VM. App::FuguVM::Remote holds the transport of the first three, and App::FuguVM::Console holds the attachment.

fuguvm ssh with no argument opens an interactive session, and it returns the exit code of ssh(1). With arguments it runs one remote command. Each argument is one word of the argument vector: the tool quotes every word, so the remote shell performs no splitting, no expansion, and no globbing. Write -- before any word that starts with a hyphen, because the option parser reads such a word as an option of the tool and exits with code 2. A caller that wants a remote shell asks for one:

fuguvm ssh -- sh -c 'cd /src && make regress'

The remote command gets no terminal and no standard input, and its output arrives when the command ends. The verb returns the exit code of the remote command. A connect failure reads as exit code 1, with a diagnostic on standard error.

fuguvm put [--mode=<octal>] <local> <remote> copies a local file or a local directory into the guest, and fuguvm get <remote> <local> copies one guest file to the host. Each <remote> must be an absolute path. A bad argument count, a relative remote path, a --mode value that is not 3 or 4 octal digits, a put source that is neither a regular file nor a directory, and a get destination that is an existing directory each give exit code 2. A failed transfer gives exit code 1. fuguvm(1) holds the full transfer rules.

fuguvm get needs Fugu::SSH->read_file. When the installed Fugu release has no such method, the verb logs the needed release and gives exit code 1.

fuguvm console attaches the terminal of the operator to the serial console of the guest, and it returns the exit code of telnet(1).

THE MIRROR COMMAND

fuguvm mirror fetch <file> downloads one file of the release of the invoked guest, verifies it through App::FuguVM::Mirror, stores it in the proxy cache, and writes the cached path to standard output, where a script can read it. The scope comes from the manifests: the tool uses the release scope when the manifest of the architecture directory names the file, and the source scope when the manifest of the version directory does. Both manifests are authoritative lists, so the tool guesses nothing, and it refuses a name that neither manifest holds with exit code 1.

fuguvm mirror fetch ports.tar.gz is the call that a consumer makes. The guest then reads a verified ports tree from the cache, over http, through the proxy.

fuguvm mirror verify verifies every cached file of the version and the architecture of the invoked guest. It logs one scope/name line for each file that failed, and one summary line with the three counts. It removes each file that failed, in its own scope only, because a file that fails a digest must not stay in a cache that a later run reads. A cached manifest pair that fails its own proof counts as failed too, and the removal covers it. The verb does not remove an unknown file: a name that no manifest holds is not a failure, and index.txt is such a name on every mirror. The verb exits 1 when one file failed, and 0 otherwise. It is idempotent: a second run over a clean cache removes nothing and exits 0. The verb always proves, whatever the verify directive says.

mirror with no action, with an unknown action, mirror fetch with no file, and mirror verify with an argument, each exit 2. An absent public key for the version, and a signify_dir that is not a directory, each exit 3. A failed download, a failed signature, and a failed digest each exit 1.

THE IMAGE EXPORT

fuguvm image export <path> [--format=qcow2|raw] writes the installed base image of the invoked VM as a full-disk image. The source is the base image of the cache entry that backs the working disk; a snapshot layer resolves to the same entry. A standalone disk, from --no-cache or image_cache no, exports the working disk itself. The default format is qcow2, and raw writes the sparse raw form.

The write goes through one temporary sibling, <path>.tmp.<pid>, and one rename, so a failure leaves no partial file behind. The command refuses a running guest with exit code 5, because a live overlay is not consistent. It refuses an existing target with exit code 1, because it must not overwrite an image that an operator published, and it creates no directory for the operator.

The report follows the key: value contract of status: bytes, format, key (empty for a standalone disk), path and source, with the two paths absolute.

EXIT CODES

The generic codes come from Fugu::CLI: 0 success, 1 error, 2 invalid arguments, 3 configuration error, 7 timeout.

Code 1 also reports an image export whose target exists, whose parent directory is absent, or whose guest has no installed disk, and an import whose base_disk file is absent at a cache miss. The four connection verbs give it for a guest that does not run, and put and get give it for a failed transfer. A failed download, a failed signature, a failed digest, a mirror verify that found one bad file, and verify yes with no signify(1) on the host, each give it too.

Code 2 also reports an unknown status key, an image call with no action, no path, or an unknown --format value, a mirror call with no action, an unknown action, or no file, and each argument refusal of put and get that the section above lists.

Code 3 also reports an arch value that is neither amd64 nor arm64, and a QEMU binary of the architecture that is not on PATH. An invalid bind_address, ssh_port, console_port or qemu_version value gives the same code. So does a QEMU version that does not match the pinned qemu_version, a signify_dir that is not a directory, an absent public key for the version on a mirror call, and each refusal of the image-lifecycle directives that App::FuguVM::Config lists. An absent key on up reads as a failed image, with code 1.

These are the codes that mean something to a VM:

4   the named VM is not in the configuration
5   the VM runs and the command needs it stopped
9   an expect script failed, the install and autoinstall
    scripts included
11  the named snapshot does not exist

Code 9 also reports the installer prompt "Continue without verification?" under verify yes: the script stops on it, because the prompt means that the mirror data is wrong.

Code 11 is scriptable: a script that runs snapshot restore || provision-from-scratch can tell a missing layer from a real failure.

App::FuguVM::Guest defines the codes that it returns, because this module loads App::FuguVM::Guest and the reverse import would be a cycle.

METHODS

run

App::FuguVM::CLI->run(@argv)

Parse, dispatch and return an exit code. This is the whole of bin/fuguvm.

SEE ALSO

Fugu::CLI, App::FuguVM::Config, App::FuguVM::Guest, fuguvm(1)

AUTHOR

Dick Olsson <hi@senzilla.io>