Skip to content

Commit

Permalink
rename to neurodocker.py + add version + documentation fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubk committed Jul 7, 2017
1 parent 91410a0 commit d2210d9
Showing 1 changed file with 220 additions and 0 deletions.
220 changes: 220 additions & 0 deletions neurodocker/neurodocker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
#!/usr/bin/env python
"""Command-line utility to generate Dockerfiles, build Docker images, run
commands within containers, and get command ouptut.
Example:
neurodocker -b ubuntu:17.04 -p apt \\
--ants version=2.1.0 \\
--freesurfer version=6.0.0 license_path="./license.txt" \\
--fsl version=5.0.10 \\
--miniconda python_version=3.5.1 \\
conda_install="traits pandas" \\
pip_install="nipype" \\
--mrtrix3 use_binaries=false \\
--spm version=12 matlab_version=R2017a \\
--neurodebian os_codename=zesty download_server=usa-nh pkgs="dcm2niix" \\
--instruction='ENTRYPOINT ["entrypoint.sh"]'
"""
# Author: Jakub Kaczmarzyk <[email protected]>

from __future__ import absolute_import, unicode_literals
from argparse import ArgumentParser, RawDescriptionHelpFormatter
import sys

from neurodocker import (__version__, Dockerfile, SpecsParser,
SUPPORTED_SOFTWARE)


def create_parser():
"""Return command-line argument parser."""
parser = ArgumentParser(description=__doc__,
formatter_class=RawDescriptionHelpFormatter)

# Global requirements.
reqs = parser.add_argument_group(title="global requirements")
reqs.add_argument("-b", "--base", required=True,
help="Base Docker image. Eg, ubuntu:17.04")
reqs.add_argument("-p", "--pkg-manager", required=True,
help="Linux package manager {apt, yum}")

_neuro_servers = ", ".join(SUPPORTED_SOFTWARE['neurodebian'].SERVERS.keys())

# Software package options.
pkgs_help = {
"all": ("Install software packages. Each argument takes a list of "
"key=value pairs. Where applicable, the default installation "
"behavior is to install by downloading and uncompressing "
"binaries."),
"ants": ("Install ANTs. Valid keys are version (required), "
"use_binaries (default true), and git_hash. If use_binaries="
"true, installs pre-compiled binaries; if use_binaries=false, "
"builds ANTs from source. If use_binaries is a URL, download "
"tarball of ANTs binaries from that URL. If git_hash is "
"specified, build from source from that commit."),
"freesurfer": ("Install FreeSurfer. Valid keys are version (required),"
"license_path (relative path to license), and "
"use_binaries (default true). A FreeSurfer license is "
"required to run the software and is not provided by "
"Neurodocker."),
"fsl": ("Install FSL. Valid keys are version (required), use_binaries "
"(default true) and use_installer."),
"miniconda": ("Install Miniconda. Valid keys are python_version "
"(required), conda_install, pip_install, and "
"miniconda_version (defaults to latest). The options "
"conda_install and pip_install accept strings of "
'packages: conda_install="traits numpy".'),
"mrtrix3": ("Install MRtrix3. Valid keys are use_binaries (default "
"true) and git_hash. If git_hash is specified and "
"use_binaries is false, will checkout to that commit "
"before building."),
"neurodebian": ("Add NeuroDebian repository and optionally install "
"NeuroDebian packages. Valid keys are os_codename "
"(required; e.g., 'zesty'), download_server "
"(required), full (if false, default, use libre "
"packages), and pkgs (list of packages to install). "
"Valid download servers are {}."
"".format(_neuro_servers)),
"spm": ("Install SPM (and its dependency, Matlab Compiler Runtime). "
"Valid keys are version and matlab_version."),
}

pkgs = parser.add_argument_group(title="software package arguments",
description=pkgs_help['all'])

list_of_kv = lambda kv: kv.split("=")

for p in SUPPORTED_SOFTWARE.keys():
flag = "--{}".format(p)

# MRtrix3 does not need any arguments by default.
if p == "mrtrix3":
pkgs.add_argument(flag, dest=p, action="append", nargs="*",
metavar="", type=list_of_kv, help=pkgs_help[p])
continue

pkgs.add_argument(flag, dest=p, action="append", nargs="+", metavar="",
type=list_of_kv, help=pkgs_help[p])


# Docker-related arguments.
other = parser.add_argument_group(title="other options")
other.add_argument('-i', '--instruction', action="append",
help=("Arbitrary Dockerfile instruction. Can be used "
"multiple times."))
other.add_argument('--no-print-df', dest='no_print_df', action="store_true",
help="Do not print the Dockerfile")
other.add_argument('-o', '--output', dest="output",
help="If specified, save Dockerfile to file with this name.")
# other.add_argument('--build', dest="build", action="store_true")


# Other options.
parser.add_argument("--check-urls", dest="check_urls", action="store_true",
help=("Verify communication with URLs used in "
"the build."), default=True,)
parser.add_argument("--no-check-urls", action="store_false", dest="check_urls",
help=("Do not verify communication with URLs used in "
"the build."))
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-V", "--version", action="version",
version=('neurodocker version {version}'
.format(version=__version__)))

return parser


def parse_args(args):
"""Return namespace of command-line arguments."""
parser = create_parser()
return parser.parse_args(args)


def _list_to_dict(list_of_kv):
"""Convert list of [key, value] pairs to a dictionary."""
if list_of_kv is not None:
list_of_kv = [item for sublist in list_of_kv for item in sublist]

for kv_pair in list_of_kv:
if len(kv_pair) != 2:
raise ValueError("Error in arguments '{}'. Did you forget "
"the equals sign?".format(kv_pair[0]))
if not kv_pair[-1]:
raise ValueError("Option required for '{}'".format(kv_pair[0]))

return {k: v for k, v in list_of_kv}

def _string_vals_to_bool(dictionary):
"""Convert string values to bool."""
import re

bool_vars = ['use_binaries', 'use_installer', 'use_neurodebian']

if dictionary is None:
return

for key in dictionary.keys():
if key in bool_vars:
if re.search('false', dictionary[key], re.IGNORECASE):
dictionary[key] = False
elif re.search('true', dictionary[key], re.IGNORECASE):
dictionary[key] = True
else:
dictionary[key] = bool(int(dictionary[key]))


def convert_args_to_specs(namespace):
"""Convert namespace of command-line arguments to dictionary compatible
with `neurodocker.parser.SpecsParser`.
"""
from copy import deepcopy

specs = vars(deepcopy(namespace))

for pkg in SUPPORTED_SOFTWARE.keys():
specs[pkg] = _list_to_dict(specs[pkg])
_string_vals_to_bool(specs[pkg])

try:
specs['miniconda']['conda_install'] = \
specs['miniconda']['conda_install'].replace(',', ' ')
except (KeyError, TypeError):
pass

try:
specs['miniconda']['pip_install'] = \
specs['miniconda']['pip_install'].replace(',', ' ')
except (KeyError, TypeError):
pass

return specs


def main(args=None):

if args is None:
namespace = parse_args(sys.argv[1:])
else:
namespace = parse_args(args)

# Create dictionary of specifications.
specs = convert_args_to_specs(namespace)

keys_to_remove = ['verbose', 'no_print_df', 'output', 'build']
for key in keys_to_remove:
specs.pop(key, None)

# Parse to double-check that keys are correct.
parser = SpecsParser(specs)

# Generate Dockerfile.
df = Dockerfile(parser.specs)
if not namespace.no_print_df:
print(df.cmd)

if namespace.output:
df.save(namespace.output)


if __name__ == "__main__": # pragma: no cover
main()

0 comments on commit d2210d9

Please sign in to comment.