kopia lustrzana https://github.com/jupyterhub/repo2docker
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!pull/42/head
rodzic
c1816a82d4
commit
64bf17513f
|
@ -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,19 +119,74 @@ class Repo2Docker(Application):
|
|||
self.log.error('Failed to clone repository!', extra=dict(phase='failed'))
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
for line in execute_cmd(['git', 'reset', '--hard', ref], cwd=checkout_path,
|
||||
capture=self.json_logs):
|
||||
self.log.info(line, extra=dict(phase='fetching'))
|
||||
except subprocess.CalledProcessError:
|
||||
self.log.error('Failed to check out ref %s', ref, 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):
|
||||
self.log.info(line, extra=dict(phase='fetching'))
|
||||
except subprocess.CalledProcessError:
|
||||
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"
|
||||
)
|
||||
|
||||
if self.json_logs:
|
||||
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
|
||||
|
||||
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,8 +260,10 @@ 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'))
|
||||
container.kill()
|
||||
if container.status == 'running':
|
||||
container.kill()
|
||||
container.remove()
|
||||
|
||||
def _get_free_port(self):
|
||||
|
|
Ładowanie…
Reference in New Issue