Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Sep 1, 2021. It is now read-only.

Commit b07080d

Browse files
author
Dario Berzano
committed
Improve feedback and help on user options (#138)
* Wrapper class for handling options * Warn if using opts only effective at alidock start * Automatically generate config example Fixes #112 and #73
1 parent 9bc835e commit b07080d

File tree

2 files changed

+131
-48
lines changed

2 files changed

+131
-48
lines changed

alidock/__init__.py

Lines changed: 74 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import print_function
44
import argparse
5-
from argparse import ArgumentParser
65
from time import time, sleep
76
from datetime import datetime as dt
87
from io import open
@@ -23,6 +22,7 @@
2322
import requests
2423
from requests.exceptions import RequestException
2524
from pkg_resources import resource_string, parse_version, require
25+
from alidock.argumentparser import AliDockArgumentParser
2626
from alidock.log import Log
2727
from alidock.util import splitEsc, getUserId, getUserName, execReturn, deactivateVenv, checkRocm
2828

@@ -42,7 +42,15 @@ def __init__(self, overrideConf=None):
4242
self.cli = docker.from_env()
4343
self.dirInside = "/home/alidock"
4444
self.userName = getUserName()
45-
self.conf = {
45+
self.conf = self.getDefaultConf()
46+
self.parseConfig()
47+
self.overrideConfig(overrideConf)
48+
self.conf["dockName"] = "{dockName}-{userId}".format(dockName=self.conf["dockName"],
49+
userId=getUserId())
50+
51+
@staticmethod
52+
def getDefaultConf():
53+
return {
4654
"dockName" : "alidock",
4755
"imageName" : "alipier/alidock:latest",
4856
"dirOutside" : os.path.join("~", "alidock"),
@@ -56,10 +64,6 @@ def __init__(self, overrideConf=None):
5664
"web" : False,
5765
"debug" : False
5866
}
59-
self.parseConfig()
60-
self.overrideConfig(overrideConf)
61-
self.conf["dockName"] = "{dockName}-{userId}".format(dockName=self.conf["dockName"],
62-
userId=getUserId())
6367

6468
def parseConfig(self):
6569
confFile = os.path.join(os.path.expanduser("~"), ".alidock-config.yaml")
@@ -425,11 +429,12 @@ def updateFunc():
425429
updateFunc=updateFunc)
426430

427431
def entrypoint():
428-
argp = ArgumentParser()
429-
argp.add_argument("--quiet", "-q", dest="quiet", default=False, action="store_true",
430-
help="Do not print any message")
431-
argp.add_argument("--version", "-v", dest="version", default=False, action="store_true",
432-
help="Print current alidock version on stdout")
432+
argp = AliDockArgumentParser(atStartTitle="only valid if container is not running, "
433+
"not effective otherwise")
434+
argp.addArgument("--quiet", "-q", dest="quiet", default=False, action="store_true",
435+
help="Do not print any message")
436+
argp.addArgument("--version", "-v", dest="version", default=False, action="store_true",
437+
help="Print current alidock version on stdout")
433438

434439
# tmux: both normal and terminal integration ("control mode")
435440
tmuxArgs = argp.add_mutually_exclusive_group()
@@ -441,38 +446,40 @@ def entrypoint():
441446
"(integration with your terminal)")
442447

443448
# The following switches can be set in a configuration file
444-
argp.add_argument("--name", dest="dockName", default=None,
445-
help="Override default container name [dockName]")
446-
argp.add_argument("--image", dest="imageName", default=None,
447-
help="Override default image name [imageName]")
448-
argp.add_argument("--shared", dest="dirOutside", default=None,
449-
help="Override host path of persistent home [dirOutside]")
450-
argp.add_argument("--mount", dest="mount", default=None, nargs="+",
451-
help="Host directories to mount under /mnt inside alidock, in the format "
452-
"/external/path[:label[:[rw|ro]]] [mount]")
453-
argp.add_argument("--update-period", dest="updatePeriod", default=None,
454-
help="Override update check period [updatePeriod]")
455-
argp.add_argument("--no-update-image", dest="dontUpdateImage", default=None,
456-
action="store_true",
457-
help="Do not update the Docker image [dontUpdateImage]")
458-
argp.add_argument("--no-update-alidock", dest="dontUpdateAlidock", default=None,
459-
action="store_true",
460-
help="Do not update alidock automatically [dontUpdateAlidock]")
461-
argp.add_argument("--nvidia", dest="useNvidiaRuntime", default=None,
462-
action="store_true",
463-
help="Launch container using the NVIDIA Docker runtime [useNvidiaRuntime]")
464-
argp.add_argument("--rocm", dest="enableRocmDevices", default=None,
465-
action="store_true",
466-
help="Expose devices needed by ROCm [enableRocmDevices]")
467-
argp.add_argument("--cvmfs", dest="cvmfs", default=None,
468-
action="store_true",
469-
help="Mount CVMFS inside the container [cvmfs]")
470-
argp.add_argument("--web", dest="web", default=None,
471-
action="store_true",
472-
help="Make X11 available from a web browser [web]")
473-
argp.add_argument("--debug", dest="debug", default=None,
474-
action="store_true",
475-
help="Increase verbosity [debug]")
449+
argp.addArgument("--name", dest="dockName", default=None, config=True,
450+
help="Override default container name")
451+
argp.addArgument("--update-period", dest="updatePeriod", default=None, config=True,
452+
help="Override update check period")
453+
argp.addArgument("--no-update-alidock", dest="dontUpdateAlidock", default=None, config=True,
454+
action="store_true",
455+
help="Do not update alidock automatically")
456+
argp.addArgument("--debug", dest="debug", default=None, config=True,
457+
action="store_true",
458+
help="Increase verbosity")
459+
460+
# Args valid only when starting the container; they can be set in a config file
461+
argp.addArgumentStart("--image", dest="imageName", default=None, config=True,
462+
help="Override default image name")
463+
argp.addArgumentStart("--shared", dest="dirOutside", default=None, config=True,
464+
help="Override host path of persistent home")
465+
argp.addArgumentStart("--mount", dest="mount", default=None, nargs="+", config=True,
466+
help="Host dirs to mount under /mnt inside alidock, in the format "
467+
"/external/path[:label[:[rw|ro]]]")
468+
argp.addArgumentStart("--no-update-image", dest="dontUpdateImage", default=None, config=True,
469+
action="store_true",
470+
help="Do not update the Docker image")
471+
argp.addArgumentStart("--nvidia", dest="useNvidiaRuntime", default=None, config=True,
472+
action="store_true",
473+
help="Use the NVIDIA Docker runtime")
474+
argp.addArgumentStart("--rocm", dest="enableRocmDevices", default=None, config=True,
475+
action="store_true",
476+
help="Expose devices needed by ROCm")
477+
argp.addArgumentStart("--cvmfs", dest="cvmfs", default=None, config=True,
478+
action="store_true",
479+
help="Mount CVMFS inside the container")
480+
argp.addArgumentStart("--web", dest="web", default=None, config=True,
481+
action="store_true",
482+
help="Make X11 available from a web browser")
476483

477484
argp.add_argument("action", default="enter", nargs="?",
478485
choices=["enter", "root", "exec", "start", "status", "stop"],
@@ -481,13 +488,13 @@ def entrypoint():
481488
argp.add_argument("shellCmd", nargs=argparse.REMAINDER,
482489
help="Command to execute in the container (works with exec)")
483490

484-
491+
argp.genConfigHelp(AliDock.getDefaultConf())
485492
args = argp.parse_args()
486493

487494
LOG.setQuiet(args.quiet)
488495

489496
try:
490-
processActions(args)
497+
processActions(args, argp.argsAtStart)
491498
except AliDockError as exc:
492499
LOG.error("Cannot continue: {msg}".format(msg=exc))
493500
exit(10)
@@ -498,7 +505,22 @@ def entrypoint():
498505
LOG.error("Cannot communicate to Docker, is it running? Full error: {msg}".format(msg=exc))
499506
exit(12)
500507

501-
def processEnterStart(aliDock, args):
508+
def checkArgsAtStart(args, argsAtStart):
509+
ignoredArgs = []
510+
for sta in argsAtStart:
511+
if args.__dict__[sta.config] is not None:
512+
ignoredArgs.append(sta.option)
513+
if ignoredArgs:
514+
LOG.warning("The following options are being ignored:")
515+
for ign in ignoredArgs:
516+
LOG.warning(" " + ign)
517+
LOG.warning("This is because alidock is already running and they are only valid when a "
518+
"new container is started.")
519+
LOG.warning("You may want to stop alidock first with:")
520+
LOG.warning(" alidock stop")
521+
LOG.warning("and try again. Check `alidock --help` for more information")
522+
523+
def processEnterStart(aliDock, args, argsAtStart):
502524
created = False
503525
if not aliDock.isRunning():
504526
created = True
@@ -514,6 +536,10 @@ def processEnterStart(aliDock, args):
514536

515537
LOG.info("Creating container, hold on")
516538
aliDock.run()
539+
else:
540+
# Container is running. Check if user has specified parameters that will be ignored and warn
541+
checkArgsAtStart(args, argsAtStart)
542+
517543
if args.action == "enter":
518544
if (args.tmux or args.tmuxControl) and os.environ.get("TMUX") is None:
519545
LOG.info("Resuming tmux session in the container")
@@ -550,7 +576,7 @@ def processStop(aliDock):
550576
LOG.info("Shutting down the container")
551577
aliDock.stop()
552578

553-
def processActions(args):
579+
def processActions(args, argsAtStart):
554580

555581
if args.version:
556582
ver = str(require(__package__)[0].version)
@@ -578,7 +604,7 @@ def processActions(args):
578604
LOG.warning("Cannot check for alidock updates this time")
579605

580606
if args.action in ["enter", "exec", "root", "start"]:
581-
processEnterStart(aliDock, args)
607+
processEnterStart(aliDock, args, argsAtStart)
582608
elif args.action == "status":
583609
processStatus(aliDock)
584610
elif args.action == "stop":

alidock/argumentparser.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import os.path
2+
import argparse
3+
from collections import namedtuple
4+
import yaml
5+
6+
AliDockArg = namedtuple("AliDockArg", "option config descr")
7+
8+
class AliDockArgumentParser(argparse.ArgumentParser):
9+
"""Lightweight subclass of ArgumentParser implementing some features required to display the
10+
alidock help: addition of normal arguments, addition of arguments only valid when starting a
11+
new container, and matching between command-line options and configuration file options."""
12+
13+
def __init__(self, atStartTitle):
14+
self.argsNormal = []
15+
self.argsAtStart = []
16+
super(AliDockArgumentParser, self).__init__(formatter_class=argparse.RawTextHelpFormatter)
17+
self.groupAtStart = self.add_argument_group(atStartTitle)
18+
19+
def addArgument(self, *args, **kwargs):
20+
configVar = None
21+
if kwargs.get("config", False):
22+
configVar = kwargs["dest"]
23+
kwargs["help"] = kwargs.get("help", "") + " [" + configVar + "]"
24+
atStart = kwargs.get("atStart", False)
25+
for key in ("atStart", "config"):
26+
try:
27+
del kwargs[key]
28+
except KeyError:
29+
pass
30+
if atStart:
31+
self.argsAtStart.append(AliDockArg(args[0], configVar, kwargs.get("help", "")))
32+
return self.groupAtStart.add_argument(*args, **kwargs)
33+
self.argsNormal.append(AliDockArg(args[0], configVar, kwargs.get("help", "")))
34+
return self.add_argument(*args, **kwargs)
35+
36+
def addArgumentStart(self, *args, **kwargs):
37+
kwargs["atStart"] = True
38+
return self.addArgument(*args, **kwargs)
39+
40+
def genConfigHelp(self, defaultConf):
41+
confFile = os.path.join(os.path.expanduser("~"), ".alidock-config.yaml")
42+
epilog = "it is possible to specify the most frequently used options in a YAML " \
43+
"configuration file in {confFile}\n" \
44+
"the following options (along with their default values) can be specified " \
45+
"(please include `---` as first line):\n---\n".format(confFile=confFile)
46+
yamlLines = {}
47+
longest = 0
48+
for opt in self.argsNormal + self.argsAtStart:
49+
if opt.config:
50+
assert opt.config in defaultConf, "option %s expected in default conf" % opt.config
51+
yamlLines[opt.option] = yaml.dump({opt.config: defaultConf[opt.config]}).rstrip()
52+
longest = max(longest, len(yamlLines[opt.option]))
53+
fmt = "%%-%ds # same as option %%s\n" % longest
54+
for yLine in yamlLines:
55+
epilog += fmt % (yamlLines[yLine], yLine)
56+
57+
self.epilog = epilog

0 commit comments

Comments
 (0)