Switch to using argparse for commandline parsing
Is cleaner and less hacky than using Traitlets. We still
use traitlets for configuration, and accept a config file
for configuring detectors and what not.
Also implements allowing arbitrary commands from the commandline,
so we can do things like build and test a container!
MODIFY
repo2docker/app.py
repo2docker/app.py
@@ -13,6 +13,7 @@ import time
import logging
import uuid
import shutil
+import argparse
from pythonjsonlogger import jsonlogger
import escapism
@@ -47,51 +48,10 @@ class Repo2Docker(Application):
version = __version__
description = __doc__
- config_file = Unicode(
- 'repo2docker_config.py',
- config=True,
- help="""
- Path to read traitlets configuration file from.
- """
- )
-
@default('log_level')
def _default_log_level(self):
return logging.INFO
- repo = Unicode(
- os.getcwd(),
- allow_none=True,
- config=True,
- help="""
- The git repository to clone.
-
- Could be a git URL or a file path.
- """
- )
-
- ref = Unicode(
- 'master',
- allow_none=True,
- config=True,
- help="""
- The git ref in the git repository to build.
-
- Can be a tag, ref or branch.
- """
- )
-
- output_image_spec = Unicode(
- None,
- allow_none=True,
- config=True,
- help="""
- The spec of the image to build.
-
- Should be the same as the value passed to `-t` param of docker build.
- """
- )
-
git_workdir = Unicode(
"/tmp",
config=True,
@@ -149,27 +109,6 @@ class Repo2Docker(Application):
DANGEROUS WHEN DONE IN A CLOUD ENVIRONMENT! ONLY USE LOCALLY!
"""
)
- json_logs = Bool(
- False,
- config=True,
- help="""
- Enable JSON logging for easier consumption by external services.
- """
- )
-
- aliases = Dict({
- 'repo': 'Repo2Docker.repo',
- 'ref': 'Repo2Docker.ref',
- 'image': 'Repo2Docker.output_image_spec',
- 'f': 'Repo2Docker.config_file',
- })
-
- flags = Dict({
- 'no-clean': ({'Repo2Docker': {'cleanup_checkout': False}}, 'Do not clean up git checkout'),
- 'no-run': ({'Repo2Docker': {'run': False}}, 'Do not run built container image'),
- 'push': ({'Repo2Docker': {'push': True}}, 'Push built image to a docker registry'),
- 'json-logs': ({'Repo2Docker': {'json_logs': True}}, 'Enable JSON logging'),
- })
def fetch(self, url, ref, checkout_path):
try:
@@ -180,6 +119,7 @@ class Repo2Docker(Application):
self.log.error('Failed to clone repository!', extra=dict(phase='failed'))
sys.exit(1)
+ if ref:
try:
for line in execute_cmd(['git', 'reset', '--hard', ref], cwd=checkout_path,
capture=self.json_logs):
@@ -188,11 +128,65 @@ class Repo2Docker(Application):
self.log.error('Failed to check out ref %s', ref, extra=dict(phase='failed'))
sys.exit(1)
- def initialize(self, *args, **kwargs):
- super().initialize(*args, **kwargs)
- self.load_config_file(self.config_file)
+ def get_argparser(self):
+ argparser = argparse.ArgumentParser()
+ argparser.add_argument(
+ '--config',
+ default='repo2docker_config.py',
+ help="Path to config file for repo2docker"
+ )
+
+ argparser.add_argument(
+ '--json-logs',
+ default=False,
+ action='store_true',
+ help='Emit JSON logs instead of human readable logs'
+ )
+
+ argparser.add_argument(
+ 'repo',
+ help='Path to repository that should be built'
+ )
+
+ argparser.add_argument(
+ '--image-name',
+ help='Name of image to be built. If unspecified will be autogenerated'
+ )
+
+ argparser.add_argument(
+ '--push',
+ dest='push',
+ action='store_true',
+ help='Push docker image to repository'
+ )
+
+ argparser.add_argument(
+ '--no-run',
+ dest='run',
+ action='store_false',
+ help='Do not run container after it has been built'
+ )
+
+ argparser.add_argument(
+ 'cmd',
+ nargs='*',
+ help='Custom command to run after building container'
+ )
+
+ return argparser
- if self.json_logs:
+ def initialize(self):
+ args = self.get_argparser().parse_args()
+
+ self.load_config_file(args.config)
+
+ if '@' in args.repo:
+ self.repo, self.ref = args.repo.split('@', 1)
+ else:
+ self.repo = args.repo
+ self.ref = None
+
+ if args.json_logs:
# Need to reset existing handlers, or we repeat messages
logHandler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter()
@@ -208,18 +202,18 @@ class Repo2Docker(Application):
# We don't want a [Repo2Docker] on all messages
self.log.handlers[0].formatter = logging.Formatter(fmt='%(message)s')
- if len(self.extra_args) == 1:
- # accept repo as a positional arg
- self.repo = self.extra_args[0]
- elif len(self.extra_args) > 1:
- print("%s accepts at most one positional argument." % self.name, file=sys.stderr)
- print("See python -m repo2docker --help for usage", file=sys.stderr)
- self.exit(1)
-
- if self.output_image_spec is None:
+ if args.image_name:
+ self.output_image_spec = args.image_name
+ else:
# Attempt to set a sane default!
# HACK: Provide something more descriptive?
- self.output_image_spec = escapism.escape(self.repo).lower() + ':' + self.ref.lower()
+ self.output_image_spec = 'image-' + escapism.escape(self.repo).lower()
+
+ self.push = args.push
+ self.run = args.run
+ self.json_logs = args.json_logs
+
+ self.run_cmd = args.cmd
def push_image(self):
@@ -245,11 +239,18 @@ class Repo2Docker(Application):
def run_image(self):
client = docker.from_env(version='auto')
port = self._get_free_port()
+ if not self.run_cmd:
+ port = str(self._get_free_port())
+ run_cmd = ['jupyter', 'notebook', '--ip', '0.0.0.0', '--port', port]
+ ports={'%s/tcp' % port: port}
+ else:
+ run_cmd = self.run_cmd
+ ports = {}
container = client.containers.run(
self.output_image_spec,
- ports={'%s/tcp' % port: port},
+ ports={},
detach=True,
- command=['jupyter', 'notebook', '--ip', '0.0.0.0', '--port', str(port)],
+ command=run_cmd
)
while container.status == 'created':
time.sleep(0.5)
@@ -259,7 +260,9 @@ class Repo2Docker(Application):
for line in container.logs(stream=True):
self.log.info(line.decode('utf-8'), extra=dict(phase='running'))
finally:
+ # FIXME: We should pass the container's exit code back out!
self.log.info('Stopping container...\n', extra=dict(phase='running'))
+ if container.status == 'running':
container.kill()
container.remove()

Switch to using argparse for commandline parsing

Is cleaner and less hacky than using Traitlets. We still
use traitlets for configuration, and accept a config file
for configuring detectors and what not.

Also implements allowing arbitrary commands from the commandline,
so we can do things like build and test a container!