Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ docker-pycreds
pandas >= 0.23.4
pyyaml
setuptools
spython >= 0.0.81
spython >= 0.0.81 ; platform_system!="Windows"
tabulate >= 0.8.6
tornado
websocket-client
Expand Down
6 changes: 3 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ author = C-PAC developers
author-email = [email protected]
license = mit
url = https://github.com/FCP-INDI/cpac
long-description = file: README.rst
long-description-content-type: text/x-rst
#long-description = file: README.rst
#long-description-content-type: text/x-rst; charset=UTF-8
# Change if running only on Windows, Mac or Linux (comma-separated)
platforms = any
# Add here all kinds of additional classifiers as defined under
Expand Down Expand Up @@ -40,7 +40,7 @@ install_requires =
dockerpty
docker-pycreds
pandas >= 0.23.4
spython >= 0.0.81
spython >= 0.0.81 ; platform_system!="Windows"
pyyaml
rich
tabulate >= 0.8.6
Expand Down
23 changes: 14 additions & 9 deletions src/cpac/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from cpac import __version__
from cpac.backends import Backends
from cpac.utils.osutils import IS_PLATFORM_WINDOWS
from cpac.helpers import cpac_parse_resources as parse_resources, TODOs

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -220,11 +221,12 @@ def _parser():

parse_resources.set_args(subparsers.add_parser(
'parse-resources', add_help=True, aliases=['parse_resources'],
help='\n'.join([parse_resources.__doc__.split(
parse_resources.__file__.split('/', maxsplit=-1)[-1],
maxsplit=1)[-1].strip().replace(
r'`cpac_parse_resources`', '"parse-resources"'),
'See "cpac parse-resources --help" for more information.'])))
help=parse_resources.__doc__
.replace('cpac_parse_resources.py', '')
.strip()
.replace(r'`cpac_parse_resources`', '"parse-resources"') +
'\nSee "cpac parse-resources --help" for more information.'
))

crash_parser = subparsers.add_parser(
'crash', add_help=True,
Expand Down Expand Up @@ -438,16 +440,19 @@ def run():
# parse args
parsed = parse_args(args)
if not parsed.platform and "--platform" not in args:
if parsed.image and os.path.exists(parsed.image):
if not IS_PLATFORM_WINDOWS and parsed.image and os.path.exists(parsed.image):
parsed.platform = 'singularity'
else:
parsed.platform = 'docker'
try:
main(parsed)
# fall back on Singularity if Docker not found
except (DockerException, NotFound): # pragma: no cover
parsed.platform = 'singularity'
main(parsed)
except (DockerException, NotFound) as exc: # pragma: no cover
if IS_PLATFORM_WINDOWS:
raise exc
else:
parsed.platform = 'singularity'
main(parsed)
else:
main(parsed)

Expand Down
29 changes: 1 addition & 28 deletions src/cpac/backends/__init__.py
Original file line number Diff line number Diff line change
@@ -1,28 +1 @@
class BackendMapper(object):

parameters = {}

def __init__(self, *args, **kwargs):
self.parameters = kwargs

def __call__(self, platform, parent=None):
return self._clients[platform.__class__](
platform=platform,
**self.parameters,
parent=parent
)


def Backends(platform, **kwargs):
"""
Given a string, return a Backend
"""
from .docker import Docker
from .singularity import Singularity

return(
{
'docker': Docker,
'singularity': Singularity
}[platform](**kwargs)
)
from .core import Backends, BackendMapper
34 changes: 34 additions & 0 deletions src/cpac/backends/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from cpac.utils.osutils import IS_PLATFORM_WINDOWS


class BackendMapper(object):

parameters = {}

def __init__(self, *args, **kwargs):
self.parameters = kwargs

def __call__(self, platform, parent=None):
return self._clients[platform.__class__](
platform=platform,
**self.parameters,
parent=parent
)


def Backends(platform, **kwargs):
"""
Given a string, return a Backend
"""
from .docker import Docker
if not IS_PLATFORM_WINDOWS:
from .singularity import Singularity
else:
Singularity = None

return(
{
'docker': Docker,
'singularity': Singularity
}[platform](**kwargs)
)
40 changes: 27 additions & 13 deletions src/cpac/backends/docker.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import os

from cpac.utils.osutils import IS_PLATFORM_WINDOWS
from cpac.utils.utils import windows_path_to_docker

import docker
import dockerpty
from docker.errors import ImageNotFound

from cpac.backends.platform import Backend, PlatformMeta

if not IS_PLATFORM_WINDOWS:
import dockerpty

class Docker(Backend):
def __init__(self, **kwargs):
Expand Down Expand Up @@ -57,8 +61,8 @@ def _collect_config(self, **kwargs):
if isinstance(self.pipeline_config, str):
container_kwargs = {'image': self.image}
if os.path.exists(self.pipeline_config):
container_kwargs['volumes'] = {self.pipeline_config: {
'bind': self.pipeline_config,
container_kwargs['volumes'] = {windows_path_to_docker(self.pipeline_config): {
'bind': windows_path_to_docker(self.pipeline_config),
'mode': 'ro',
}}
try:
Expand All @@ -68,7 +72,7 @@ def _collect_config(self, **kwargs):
self.pull(**kwargs)
container = self.client.containers.create(
**container_kwargs)
stream = container.get_archive(path=self.pipeline_config)[0]
stream = container.get_archive(path=windows_path_to_docker(self.pipeline_config))[0]
self.config = b''.join([
l for l in stream # noqa E741
]).split(b'\x000000000')[-1].replace(b'\x00', b'').decode()
Expand Down Expand Up @@ -143,6 +147,13 @@ def _execute(self, command, run_type='run', **kwargs):
**self.docker_kwargs
}

if IS_PLATFORM_WINDOWS:
shared_kwargs['working_dir'] = windows_path_to_docker(shared_kwargs['working_dir'])

for i in range(len(command)):
if len(command[i]) > 1 and command[i][1] == ':':
command[i] = windows_path_to_docker(command[i])

if run_type == 'run':
self.container = self.client.containers.run(
**shared_kwargs,
Expand Down Expand Up @@ -170,15 +181,18 @@ def _execute(self, command, run_type='run', **kwargs):
stream=True
)[1]
elif run_type == 'enter':
self.container = self.client.containers.create(
**shared_kwargs,
auto_remove=True,
entrypoint='/bin/bash',
stdin_open=True,
tty=True,
detach=False
)
dockerpty.start(self.client.api, self.container.id)
if IS_PLATFORM_WINDOWS:
raise NotImplementedError()
else:
self.container = self.client.containers.create(
**shared_kwargs,
auto_remove=True,
entrypoint='/bin/bash',
stdin_open=True,
tty=True,
detach=False
)
dockerpty.start(self.client.api, self.container.id)
return container_return

def get_response(self, command, **kwargs):
Expand Down
18 changes: 13 additions & 5 deletions src/cpac/backends/platform.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""Base classes for platform-specific implementations"""
from cpac.utils.osutils import IS_PLATFORM_WINDOWS

import atexit
import os
import pwd
if not IS_PLATFORM_WINDOWS:
import pwd
import tempfile
import textwrap

Expand Down Expand Up @@ -411,10 +414,15 @@ def _set_bindings(self, **kwargs):
if kwargs.get('config_bindings'):
for binding in kwargs['config_bindings']:
self._bind_volume(binding)
self.uid = os.getuid()
pwuid = pwd.getpwuid(self.uid)
self.username = getattr(pwuid, 'pw_name',
getattr(pwuid, 'pw_gecos', str(self.uid)))

if IS_PLATFORM_WINDOWS:
self.username = os.getlogin()
self.uid = hash(self.username) % 100 # TODO: Is there something we have to keep in mind here?
else:
self.uid = os.getuid()
pwuid = pwd.getpwuid(self.uid)
self.username = getattr(pwuid, 'pw_name',
getattr(pwuid, 'pw_gecos', str(self.uid)))
self.bindings.update({
'tag': tag,
'uid': self.uid,
Expand Down
Empty file added src/cpac/checks/__init__.py
Empty file.
32 changes: 32 additions & 0 deletions src/cpac/checks/checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Functions to check things like the in-container C-PAC version."""
from semver import VersionInfo

from cpac.backends import Backends


def check_version_at_least(min_version, platform, image=None, tag=None):
"""Function to check the in-container C-PAC version

Parameters
----------
min_version : str
Semantic version

platform : str or None

image : str or None

tag : str or None

Returns
-------
bool
Is the version at least the minimum version?
"""
if platform is None:
platform = 'docker'
arg_vars = {'platform': platform, 'image': image, 'tag': tag,
'command': 'version'}
return VersionInfo.parse(min_version) <= VersionInfo.parse(
Backends(**arg_vars).run(
run_type='version').versions.CPAC.lstrip('v'))
3 changes: 3 additions & 0 deletions src/cpac/utils/osutils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import platform

IS_PLATFORM_WINDOWS = platform.system() == 'Windows'
19 changes: 19 additions & 0 deletions src/cpac/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

import os

import pathlib as pl
from os import PathLike
from typing import Union

from itertools import permutations
from typing import Iterator, overload
from warnings import warn

import yaml

from cpac import dist_name
from cpac.utils.osutils import IS_PLATFORM_WINDOWS


class LocalsToBind:
Expand Down Expand Up @@ -175,6 +180,18 @@ def _warn_if_undefined(self):
return False


def windows_path_to_docker(path: Union[str, PathLike]):
if isinstance(path, str) and ':' not in path:
return path

win_path = pl.Path(path)
if win_path.is_absolute():
win_drive = win_path.drive
win_dir = win_path.relative_to(win_drive).as_posix()
return f'/{win_drive[0].lower()}{win_dir}'
return win_path.as_posix()


class Volume:
'''Class to store bind volume information'''
@overload
Expand All @@ -201,6 +218,8 @@ def __repr__(self):
return str(self)

def __str__(self):
if IS_PLATFORM_WINDOWS:
return f'{windows_path_to_docker(self.local)}:{windows_path_to_docker(self.bind)}:{self.mode}'
return f'{self.local}:{self.bind}:{self.mode}'


Expand Down