mirror of
https://github.com/projectatomic/atomic.git
synced 2026-02-05 18:45:01 +01:00
Atomic/help.py: Display man-like help for an image
Images or containers can now have an associated man-like help page to help users understand more about the image. Typical information included are things like a longer description, if the image needs to be installed, security implications, steps to upgrade, etc. The default behavior is for atomic to display a file called help.1 (in man format) located in the / of the docker object. This default can be overriden with the HELP LABEL. The HELP LABEL needs to be a fully qualified command to work correctly.
This commit is contained in:
95
Atomic/help.py
Normal file
95
Atomic/help.py
Normal file
@@ -0,0 +1,95 @@
|
||||
from . import Atomic
|
||||
import subprocess
|
||||
from pydoc import pager
|
||||
import os
|
||||
from . import mount
|
||||
import sys
|
||||
from . import util
|
||||
|
||||
|
||||
class AtomicHelp(Atomic):
|
||||
|
||||
def __init__(self):
|
||||
super(AtomicHelp, self).__init__()
|
||||
self.mount_location = '/run/atomic'
|
||||
self.help_file_name = 'help.1'
|
||||
self.docker_object = None
|
||||
self.is_container = True
|
||||
self.use_pager = True
|
||||
self.alt_help_cmd = None
|
||||
|
||||
def help(self):
|
||||
"""
|
||||
Displays help text for a container.
|
||||
:return: None
|
||||
"""
|
||||
self.docker_object = self.args.image
|
||||
docker_id = self.get_input_id(self.docker_object)
|
||||
self.inspect = self._inspect_container(docker_id)
|
||||
if self.inspect is None: # docker_id is an image
|
||||
self.inspect = self._inspect_image(docker_id)
|
||||
self.is_container = False
|
||||
else:
|
||||
# The docker object is a container, need to set
|
||||
# its image
|
||||
self.image = self.inspect['Image']
|
||||
|
||||
# Check if an alternate help command is provided
|
||||
labels = self._get_labels()
|
||||
self.alt_help_cmd = None if len(labels) == 0 else labels.get('HELP')
|
||||
|
||||
if self.alt_help_cmd is not None:
|
||||
self.display_alt_help()
|
||||
else:
|
||||
self.display_man_help(docker_id)
|
||||
|
||||
def display_man_help(self, docker_id):
|
||||
"""
|
||||
Display the help for a container or image using the default
|
||||
method of displaying a man formatted page
|
||||
:param docker_id: docker object to get help for
|
||||
:return: None
|
||||
"""
|
||||
if not os.path.exists(self.mount_location):
|
||||
os.mkdir(self.mount_location)
|
||||
# Set the pager to less -R
|
||||
enc = sys.getdefaultencoding()
|
||||
if sys.stdout.isatty():
|
||||
os.environ['PAGER'] = '/usr/bin/less -R'
|
||||
else:
|
||||
# There is no tty
|
||||
self.use_pager = False
|
||||
dm = mount.DockerMount(self.mount_location, mnt_mkdir=True)
|
||||
mnt_path = dm.mount(docker_id)
|
||||
try:
|
||||
help_file = open(os.path.join(mnt_path, self.help_file_name))
|
||||
except IOError:
|
||||
pass
|
||||
try:
|
||||
help_file = open(os.path.join(mnt_path, 'rootfs', self.help_file_name))
|
||||
except IOError:
|
||||
dm.unmount(path=mnt_path)
|
||||
raise ValueError("Unable to find help file for {}".format(self.docker_object))
|
||||
|
||||
cmd2 = ['groff', '-man', '-Tascii']
|
||||
c2 = subprocess.Popen(cmd2, stdin=help_file, stdout=subprocess.PIPE)
|
||||
result = c2.communicate()[0].decode(enc)
|
||||
help_file.close()
|
||||
if not self.use_pager:
|
||||
util.writeOut("\n{}\n".format(result))
|
||||
else:
|
||||
# Call the pager
|
||||
pager(result)
|
||||
|
||||
# Clean up
|
||||
dm.unmount(path=mnt_path)
|
||||
|
||||
def display_alt_help(self):
|
||||
"""
|
||||
Displays help when the HELP LABEL override is being used.
|
||||
:return: None
|
||||
"""
|
||||
cmd = self.gen_cmd(self.alt_help_cmd.split(" "))
|
||||
self.display(cmd)
|
||||
subprocess.check_call(cmd, env=self.cmd_env, shell=True)
|
||||
|
||||
@@ -208,12 +208,18 @@ class DockerMount(Mount):
|
||||
except docker.errors.APIError as ex:
|
||||
raise MountError('Error creating temporary container:\n%s' % str(ex))
|
||||
|
||||
def _clone(self, cid):
|
||||
def _clone(self, cid, image_only=False):
|
||||
"""
|
||||
Create a temporary image snapshot from a given cid.
|
||||
Create a temporary image snapshot from a given cid and then
|
||||
create temporary container from the temporary image.
|
||||
|
||||
Temporary image snapshots are marked with a sentinel label
|
||||
so that they can be cleaned on unmount.
|
||||
|
||||
image_only: Create the image from the container only
|
||||
|
||||
Return: the id of the temporary container unless image_only=True
|
||||
in which case it returns the image cloned image id.
|
||||
"""
|
||||
try:
|
||||
iid = self.client.commit(
|
||||
@@ -227,7 +233,10 @@ class DockerMount(Mount):
|
||||
except docker.errors.APIError as ex:
|
||||
raise MountError(str(ex))
|
||||
self.tmp_image = iid
|
||||
return self._create_temp_container(iid)
|
||||
if image_only:
|
||||
return iid
|
||||
else:
|
||||
return self._create_temp_container(iid)
|
||||
|
||||
def _is_container_running(self, cid):
|
||||
cinfo = self.client.inspect_container(cid)
|
||||
@@ -469,14 +478,17 @@ class DockerMount(Mount):
|
||||
if self.tmp_image is not None:
|
||||
self.client.remove_image(self.tmp_image, noprune=True)
|
||||
|
||||
def unmount(self):
|
||||
def unmount(self, path=None):
|
||||
"""
|
||||
Unmounts and cleans-up after a previous mount().
|
||||
"""
|
||||
driver = self.client.info()['Driver']
|
||||
driver_unmount_fn = getattr(self, "_unmount_" + driver,
|
||||
self._unsupported_backend)
|
||||
driver_unmount_fn()
|
||||
if path is not None:
|
||||
driver_unmount_fn(path=path)
|
||||
else:
|
||||
driver_unmount_fn()
|
||||
|
||||
def _get_all_cids(self):
|
||||
'''
|
||||
|
||||
8
atomic
8
atomic
@@ -32,6 +32,7 @@ import Atomic
|
||||
from Atomic.diff import Diff
|
||||
from Atomic.top import Top
|
||||
from Atomic.verify import Verify
|
||||
from Atomic.help import AtomicHelp
|
||||
|
||||
PROGNAME = "atomic"
|
||||
gettext.bindtextdomain(PROGNAME, "/usr/share/locale")
|
||||
@@ -129,6 +130,13 @@ if __name__ == '__main__':
|
||||
diffp.add_argument("-v", "--verbose", default=False, action='store_true',
|
||||
help=_("Show verbose output, listing all RPMs"))
|
||||
|
||||
#atomic help
|
||||
helpp = subparser.add_parser(
|
||||
"help", help=_("Display help associated with the image"),
|
||||
epilog="atomic help 'image'")
|
||||
helpp.set_defaults(_class=AtomicHelp, func='help')
|
||||
helpp.add_argument("image", help=_("Image ID or name"))
|
||||
|
||||
if os.path.exists("/usr/bin/rpm-ostree"):
|
||||
# atomic host
|
||||
hostp = subparser.add_parser("host", help=_("execute Atomic host "
|
||||
|
||||
21
bash/atomic
21
bash/atomic
@@ -525,6 +525,27 @@ _atomic_host_host() {
|
||||
esac
|
||||
}
|
||||
|
||||
_atomic_help() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "$all_options" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
|
||||
local counter=$( __atomic_pos_first_nonflag $( __atomic_to_alternatives "$options_with_args" ) )
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__atomic_containers_and_images
|
||||
return 0
|
||||
fi
|
||||
|
||||
COMPREPLY=( $( compgen -d "$cur" ) )
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
_atomic_host() {
|
||||
local commands=(
|
||||
deploy
|
||||
|
||||
32
docs/atomic-help.1.md
Normal file
32
docs/atomic-help.1.md
Normal file
@@ -0,0 +1,32 @@
|
||||
% ATOMIC(1) Atomic Man Pages
|
||||
% Brent Baude
|
||||
% January 2016
|
||||
# NAME
|
||||
atomic-help - Display help associated with a container or image
|
||||
# SYNOPSIS
|
||||
**atomic help**
|
||||
[**-h**|**--help**]
|
||||
IMAGE|CONTAINER
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
**Atomic help** displays a help file associated with a container or image.
|
||||
|
||||
If a container or image has a help file (in man format) embedded in itself, atomic help will display
|
||||
the help file in a pager similar to man. The default location for a help file is /image_help.1 but
|
||||
the location of the help can be overridden with the HELP LABEL. If you choose to override the default
|
||||
location, ensure the path provided is a fully-qualified path that includes the help file itself.
|
||||
|
||||
The help file can be written using the middleman markup and the converted using the go-md2man utility
|
||||
as follows:
|
||||
```
|
||||
go-md2man -in image_help.1.md -out image_help.1
|
||||
```
|
||||
You can also use any of the many options to create the help file including using native man tagging.
|
||||
|
||||
# OPTIONS
|
||||
**-h** **--help**
|
||||
Print usage statement
|
||||
|
||||
# HISTORY
|
||||
January 2016, Originally written by Brent Baude (bbaude at redhat dot com)
|
||||
@@ -20,6 +20,9 @@ Atomic Management Tool
|
||||
**atomic-diff(1)**
|
||||
show the differences between two images|containers' RPMs
|
||||
|
||||
**atomic-help(1)**
|
||||
show help associated with a container or image
|
||||
|
||||
**atomic-host(1)**
|
||||
execute Atomic commands
|
||||
|
||||
|
||||
11
test.sh
11
test.sh
@@ -60,7 +60,6 @@ make_docker_images () {
|
||||
chksum=$(_checksum ${df})
|
||||
IFS=$'.' read -a split <<< "${BASE_NAME}"
|
||||
iname="atomic-test-${split[1]}"
|
||||
|
||||
# If there is a matching Dockerfile.X.d, then include its contents
|
||||
# in the checksum data.
|
||||
chksum="${chksum}$(_checksum ${df}.d)"
|
||||
@@ -82,6 +81,16 @@ make_docker_images () {
|
||||
cp ${df} ${df_cp}
|
||||
printf "\nLABEL \"Checksum\"=\"${chksum}" >> ${df_cp}
|
||||
|
||||
# Copy help.1 into atomic-test-1
|
||||
if [[ ${iname} = "atomic-test-1" ]]; then
|
||||
cp ./tests/test-images/help.1 ${WORK_DIR}
|
||||
fi
|
||||
|
||||
# Copy help.sh into atomic-test-3
|
||||
if [[ ${iname} = "atomic-test-3" ]]; then
|
||||
cp ./tests/test-images/help.sh ${WORK_DIR}
|
||||
fi
|
||||
|
||||
# Remove the old image... Though there may not be one.
|
||||
set +e
|
||||
${DOCKER} rmi ${iname} &>> ${LOG}
|
||||
|
||||
32
tests/integration/test_help.sh
Executable file
32
tests/integration/test_help.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash -x
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
# Test scripts run with PWD=tests/..
|
||||
|
||||
# The test harness exports some variables into the environment during
|
||||
# testing: PYTHONPATH (python module import path
|
||||
# WORK_DIR (a directory that is safe to modify)
|
||||
# DOCKER (the docker executable location)
|
||||
# ATOMIC (an invocation of 'atomic' which measures code coverage)
|
||||
# SECRET (a generated sha256 hash inserted into test containers)
|
||||
|
||||
# In addition, the test harness creates some images for use in testing.
|
||||
# See tests/test-images/
|
||||
|
||||
OUTPUT=$(/bin/true)
|
||||
|
||||
# Test standard help in man format
|
||||
${ATOMIC} help --no_pager atomic-test-1 1>/dev/null
|
||||
|
||||
# Test override label
|
||||
${ATOMIC} help atomic-test-3 1>/dev/null
|
||||
|
||||
rc=0
|
||||
${ATOMIC} help centos:latest 1>/dev/null || rc=$?
|
||||
if [[ ${rc} != 1 ]]; then
|
||||
# Test failed
|
||||
echo "This test should result in a return code of 1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -7,3 +7,5 @@ LABEL "Name"="atomic-test-1"
|
||||
LABEL RUN "/usr/bin/docker run -t --user \${SUDO_UID}:\${SUDO_GID} \${OPT1} -v /var/log/\${NAME}:/var/log -v /var/lib/\${NAME}:/var/lib \$OPT2 --name \${NAME} \${IMAGE} \$OPT3 echo I am the run label."
|
||||
|
||||
LABEL INSTALL "/usr/bin/docker \${OPT1} run -v /etc/\${NAME}:/etc -v /var/log/\${NAME}:/var/log -v /var/lib/\${NAME}:/var/lib \$OPT2 --name \${NAME} \${IMAGE} \$OPT3 echo I am the install label."
|
||||
|
||||
COPY help.1 /
|
||||
|
||||
12
tests/test-images/Dockerfile.3
Normal file
12
tests/test-images/Dockerfile.3
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM centos
|
||||
MAINTAINER "Sally O'Malley <somalley at redhat dot com>
|
||||
ENV container docker
|
||||
|
||||
LABEL "Name"="atomic-test-3"
|
||||
|
||||
LABEL RUN "/usr/bin/docker run -t --user \${SUDO_UID}:\${SUDO_GID} \${OPT1} -v /var/log/\${NAME}:/var/log -v /var/lib/\${NAME}:/var/lib \$OPT2 --name \${NAME} \${IMAGE} \$OPT3 echo I am the run label."
|
||||
|
||||
LABEL INSTALL "/usr/bin/docker \${OPT1} run -v /etc/\${NAME}:/etc -v /var/log/\${NAME}:/var/log -v /var/lib/\${NAME}:/var/lib \$OPT2 --name \${NAME} \${IMAGE} \$OPT3 echo I am the install label."
|
||||
|
||||
LABEL HELP "docker run --rm IMAGE /usr/bin/bash /help.sh"
|
||||
COPY help.sh /
|
||||
18
tests/test-images/help.1
Normal file
18
tests/test-images/help.1
Normal file
@@ -0,0 +1,18 @@
|
||||
.TH "ATOMIC" "1" " Atomic Man Pages" "Brent Baude" "December 2015" ""
|
||||
|
||||
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
Simple \ftest\fP case
|
||||
|
||||
|
||||
.SH USAGE
|
||||
.PP
|
||||
test
|
||||
|
||||
.PP
|
||||
.RS
|
||||
|
||||
.SH HISTORY
|
||||
.PP
|
||||
December 2015, Originally written by Brent Baude (bbaude at redhat dot com)
|
||||
1
tests/test-images/help.sh
Normal file
1
tests/test-images/help.sh
Normal file
@@ -0,0 +1 @@
|
||||
echo "Testing help"
|
||||
Reference in New Issue
Block a user