Skip to content

Commit

Permalink
feat(windows): improve windows support
Browse files Browse the repository at this point in the history
  • Loading branch information
Toilal committed Oct 13, 2022
1 parent d1d522b commit 2aded1f
Show file tree
Hide file tree
Showing 16 changed files with 115 additions and 79 deletions.
13 changes: 12 additions & 1 deletion ddb/cache/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from uuid import uuid4

from slugify import slugify

from .cache import Cache
Expand All @@ -19,7 +21,16 @@ def register_project_cache(cache_name):
"""
Creates a ShelveCache for current project, and register it with given name.
"""
namespace = [item for item in (project_cache_name, config.paths.project_home, cache_name) if item]
registered_projects_cache_name = ShelveCache('project-cache-uuid', eternal=True)
try:
project_cache_uuid = registered_projects_cache_name.get(config.paths.project_home)
if project_cache_uuid is None:
project_cache_uuid = str(uuid4())
registered_projects_cache_name.set(config.paths.project_home, project_cache_uuid)
finally:
registered_projects_cache_name.close()

namespace = [item for item in (project_cache_uuid, cache_name) if item]
cache = ShelveCache(slugify('.'.join(namespace), regex_pattern=r'[^-a-z0-9_\.]+'))

caches.register(cache, cache_name)
Expand Down
11 changes: 6 additions & 5 deletions ddb/cache/shelve_cache.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
import dbm
import os
import shelve
import tempfile

from .cache import Cache
from ..config import config

import dbm

# Easy fix for https://github.com/inetum-orleans/docker-devbox-ddb/issues/49
# pylint:disable=protected-access
_dbm_names = dbm._names = ['dbm.gnu', 'dbm.ndbm', 'dbm.dumb']
Expand All @@ -23,17 +22,19 @@ class ShelveCache(Cache):
A cache implementation relying of shelve module.
"""

def __init__(self, namespace: str):
def __init__(self, namespace: str, eternal=False):
super().__init__(namespace)

clear_cache = config.clear_cache and not eternal

if config.paths.home:
path = os.path.join(config.paths.home, "cache")
else:
path = os.path.join(tempfile.gettempdir(), "ddb", "cache")
os.makedirs(path, exist_ok=True)

self.basename = os.path.join(path, self._namespace)
if config.clear_cache:
if clear_cache:
self._delete_files(self.basename)
try:
self._shelf = shelve.open(self.basename)
Expand All @@ -45,7 +46,7 @@ def __init__(self, namespace: str):
raise open_error from fallback_error
else:
raise open_error
if config.clear_cache:
if clear_cache:
self._shelf.clear()

@staticmethod
Expand Down
15 changes: 7 additions & 8 deletions ddb/feature/docker/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
import os
import pathlib
import re
from typing import Iterable, ClassVar

Expand All @@ -14,6 +13,7 @@
from ..schema import FeatureSchema
from ...action import Action
from ...config import config
from ...utils.compat import path_as_posix_fast


class DockerFeature(Feature):
Expand Down Expand Up @@ -142,10 +142,9 @@ def _configure_defaults_path_mapping(feature_config):
path_mapping = feature_config.get('path_mapping')
if path_mapping is None:
path_mapping = {}
if config.data.get('core.os') == 'nt':
raw = config.data.get('core.path.project_home')
mapped = re.sub(r"^([a-zA-Z]):", r"/\1", raw)
mapped = pathlib.Path(mapped).as_posix()
mapped = re.sub(r"(\/)(.)(\/.*)", lambda x: x.group(1) + x.group(2).lower() + x.group(3), mapped)
path_mapping[raw] = mapped
feature_config['path_mapping'] = path_mapping
if config.data.get('core.os') == 'nt':
for key in ('core.path.project_home', 'core.path.ddb_home', 'core.path.home'):
raw = config.data.get(key)
if raw:
path_mapping[raw] = path_as_posix_fast(raw)
feature_config['path_mapping'] = path_mapping
6 changes: 3 additions & 3 deletions ddb/feature/docker/binaries.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import os
import posixpath
import shlex
from pathlib import Path
from typing import Optional, Iterable

from ddb.binary.binary import AbstractBinary
from ddb.config import config
from ddb.feature.docker.lib.compose.config.errors import ConfigurationError
from ddb.feature.docker.utils import get_mapped_path, DockerComposeControl
from ddb.utils.compat import path_as_posix_fast
from ddb.utils.process import effective_command
from ddb.utils.simpleeval import simple_eval

Expand Down Expand Up @@ -48,8 +48,8 @@ def simple_eval_options(*args):
names={"args": " ".join(args),
"argv": args,
"config": config,
"cwd": str(Path(config.cwd).as_posix()) if config.cwd else None,
"project_cwd": str(Path(config.project_cwd).as_posix()) if config.project_cwd else None})
"cwd": path_as_posix_fast(config.cwd) if config.cwd else None,
"project_cwd": path_as_posix_fast(config.project_cwd) if config.project_cwd else None})

def command(self, *args) -> Iterable[str]:
cwd = config.cwd if config.cwd else os.getcwd()
Expand Down
21 changes: 21 additions & 0 deletions ddb/feature/jsonnet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .actions import JsonnetAction
from .schema import JsonnetSchema
from ...config import config
from ...utils.compat import path_as_posix_fast
from ...utils.file import TemplateFinder


Expand Down Expand Up @@ -49,6 +50,7 @@ def _configure_defaults(self, feature_config: Dotty):
self._configure_defaults_build_image_tag(feature_config)
self._configure_defaults_restart_policy(feature_config)
self._configure_defaults_https(feature_config)
self._configure_defaults_mount(feature_config)

@staticmethod
def _configure_defaults_includes(feature_config):
Expand Down Expand Up @@ -229,3 +231,22 @@ def _configure_defaults_https(feature_config):

if redirect_to_https and not https:
feature_config['docker.virtualhost.redirect_to_https'] = False

@staticmethod
def _configure_defaults_mount(feature_config):
directory = feature_config.get('docker.mount.directory')
if directory is not None:
absolute_directory = os.path.abspath(directory) if \
not os.path.isabs(directory) else directory
if os.name == 'nt':
absolute_directory = path_as_posix_fast(absolute_directory)
feature_config['docker.mount.directory'] = absolute_directory
directories = feature_config.get('docker.mount.directories')
if directories is not None:
absolute_directories = {}
for directory_key, directory_value in directories.items():
absolute_directories[directory_key] = os.path.abspath(directory_value) if \
not os.path.isabs(directory_value) else directory_value
if os.name == 'nt':
absolute_directories[directory_key] = path_as_posix_fast(absolute_directories[directory_key])
feature_config['docker.mount.directories'] = absolute_directories
6 changes: 3 additions & 3 deletions ddb/feature/jsonnet/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ def name(self) -> str:
return "jsonnet:render"

def _build_template_finder(self) -> TemplateFinder:
return TemplateFinder(config.data.get("jsonnet.includes"),
config.data.get("jsonnet.excludes"),
[],
return TemplateFinder([],
[],
config.data.get("jsonnet.includes"),
config.data.get("jsonnet.excludes"),
config.data.get("jsonnet.suffixes"))

def _render_template(self, template: str, target: str) -> Iterable[Tuple[Union[str, bytes, bool], str]]:
Expand Down
4 changes: 2 additions & 2 deletions ddb/feature/jsonnet/lib/ddb.docker.libjsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,10 @@ local apply_volumes_mounts(volumes) = {
[key]: volumes[key] +
(if _docker_mount_directories != null && std.objectHas(_docker_mount_directories, key) then {
driver: 'local',
driver_opts: {type: 'none', o: 'bind', device: _ensure_absolute(_docker_mount_directories[key])}
driver_opts: {type: 'none', o: 'bind', device: _docker_mount_directories[key]}
} else if _docker_mount_directory != null then {
driver: 'local',
driver_opts: {type: 'none', o: 'bind', device: _ensure_absolute(_docker_mount_directory + '/' + key)}
driver_opts: {type: 'none', o: 'bind', device: _docker_mount_directory + '/' + key}
}
else {})
for key in std.objectFields(volumes)
Expand Down
6 changes: 3 additions & 3 deletions ddb/feature/symlinks/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ def name(self) -> str:
return "symlinks:create"

def _build_template_finder(self) -> TemplateFinder:
return TemplateFinder(config.data.get("symlinks.includes"),
config.data.get("symlinks.excludes"),
[],
return TemplateFinder([],
[],
config.data.get("symlinks.includes"),
config.data.get("symlinks.excludes"),
config.data.get("symlinks.suffixes"))

def _render_template(self, template: str, target: str) -> Iterable[Tuple[Union[str, bytes, bool], str]]:
Expand Down
4 changes: 2 additions & 2 deletions ddb/feature/ytt/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ def _render_template(self, template: str, target: str) -> Iterable[Tuple[Union[s
config.data["ytt.depends_suffixes"],
config.data["ytt.extensions"]
)
template_finder = TemplateFinder(includes,
[],
template_finder = TemplateFinder([],
[],
includes,
[],
config.data["ytt.depends_suffixes"],
os.path.dirname(target),
Expand Down
11 changes: 11 additions & 0 deletions ddb/utils/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import re

_posix_drive_letter_regex = re.compile(r"^([a-zA-Z]):")


def path_as_posix_fast(path: str):
"""
Simpler but faster version of pathlib Path.as_posix method
"""
path = _posix_drive_letter_regex.sub(lambda match: '/' + match.group(1).lower(), path)
return path.replace('\\', '/')
15 changes: 9 additions & 6 deletions ddb/utils/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,13 @@ def _has_ancestor_excluded(candidate_path: Path, *excludes: re.Pattern) -> bool:
return False

@staticmethod
def _prefix_path_to_current_folder(path: str):
def _path_alternatives_for_pattern_match(path: str):
if path[0:2] == './':
return path
return './' + path
yield path[2:]
yield path
else:
yield path
yield './' + path

@staticmethod
def _as_posix_fast(path: str):
Expand All @@ -303,10 +306,10 @@ def match_any_pattern(candidate: str, *patterns: re.Pattern):
Check if a string match at least one of provided compiled pattern
"""
candidate = FileWalker._as_posix_fast(candidate)
norm_candidate = FileWalker._prefix_path_to_current_folder(candidate)
for pattern in patterns:
if pattern.match(candidate) or pattern.match(norm_candidate):
return True
for candidate_alternative in FileWalker._path_alternatives_for_pattern_match(candidate):
if pattern.match(candidate_alternative):
return True
return False

@staticmethod
Expand Down
31 changes: 7 additions & 24 deletions tests/feature/jsonnet/test_jsonnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from ddb.feature.docker import DockerFeature
from ddb.feature.file import FileFeature, FileWalkAction
from ddb.feature.jsonnet import JsonnetFeature
from ddb.utils.compat import path_as_posix_fast


class TestJsonnetAction:
Expand Down Expand Up @@ -270,10 +271,7 @@ def before_load_config():
expected_data = f.read()

if os.name == 'nt':
mapped_cwd = re.sub(r"^([a-zA-Z]):", r"/\1", os.getcwd())
mapped_cwd = pathlib.Path(mapped_cwd).as_posix()

expected_data = expected_data.replace("%ddb.path.project%", mapped_cwd)
expected_data = expected_data.replace("%ddb.path.project%", path_as_posix_fast(os.getcwd()))
else:
expected_data = expected_data.replace("%ddb.path.project%", os.getcwd())
expected_data = expected_data.replace("%network_name%",
Expand Down Expand Up @@ -318,10 +316,7 @@ def before_load_config():
expected_data = f.read()

if os.name == 'nt':
mapped_cwd = re.sub(r"^([a-zA-Z]):", r"/\1", os.getcwd())
mapped_cwd = pathlib.Path(mapped_cwd).as_posix()

expected_data = expected_data.replace("%ddb.path.project%", mapped_cwd)
expected_data = expected_data.replace("%ddb.path.project%", path_as_posix_fast(os.getcwd()))
else:
expected_data = expected_data.replace("%ddb.path.project%", os.getcwd())
expected_data = expected_data.replace("%network_name%",
Expand Down Expand Up @@ -365,10 +360,7 @@ def before_load_config():
expected_data = f.read()

if os.name == 'nt':
mapped_cwd = re.sub(r"^([a-zA-Z]):", r"/\1", os.getcwd())
mapped_cwd = pathlib.Path(mapped_cwd).as_posix()

expected_data = expected_data.replace("%ddb.path.project%", mapped_cwd)
expected_data = expected_data.replace("%ddb.path.project%", path_as_posix_fast(os.getcwd()))
else:
expected_data = expected_data.replace("%ddb.path.project%", os.getcwd())
expected_data = expected_data.replace("%network_name%",
Expand Down Expand Up @@ -403,10 +395,7 @@ def test_docker_compose_variables(self, project_loader):
expected_data = f.read()

if os.name == 'nt':
mapped_cwd = re.sub(r"^([a-zA-Z]):", r"/\1", os.getcwd())
mapped_cwd = pathlib.Path(mapped_cwd).as_posix()

expected_data = expected_data.replace("%ddb.path.project%", mapped_cwd)
expected_data = expected_data.replace("%ddb.path.project%", path_as_posix_fast(os.getcwd()))
else:
expected_data = expected_data.replace("%ddb.path.project%", os.getcwd())
expected_data = expected_data.replace("%network_name%",
Expand Down Expand Up @@ -441,10 +430,7 @@ def test_docker_compose_project_dot_com(self, project_loader):
expected_data = f.read()

if os.name == 'nt':
mapped_cwd = re.sub(r"^([a-zA-Z]):", r"/\1", os.getcwd())
mapped_cwd = pathlib.Path(mapped_cwd).as_posix()

expected_data = expected_data.replace("%ddb.path.project%", mapped_cwd)
expected_data = expected_data.replace("%ddb.path.project%", path_as_posix_fast(os.getcwd()))
else:
expected_data = expected_data.replace("%ddb.path.project%", os.getcwd())
expected_data = expected_data.replace("%network_name%",
Expand Down Expand Up @@ -543,10 +529,7 @@ def test_docker_compose_variants(self, project_loader, variant):
expected_data = f.read()

if os.name == 'nt':
mapped_cwd = re.sub(r"^([a-zA-Z]):", r"/\1", os.getcwd())
mapped_cwd = pathlib.Path(mapped_cwd).as_posix()

expected_data = expected_data.replace("%ddb.path.project%", mapped_cwd)
expected_data = expected_data.replace("%ddb.path.project%", path_as_posix_fast(os.getcwd()))
else:
expected_data = expected_data.replace("%ddb.path.project%", os.getcwd())
expected_data = expected_data.replace("%network_name%",
Expand Down
13 changes: 8 additions & 5 deletions tests/feature/run/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,14 @@ def test_run_docker_binary_workdir_outside(self, project_loader, capsys: Capture
cwd = config.cwd if config.cwd else os.getcwd()
real_cwd = os.path.realpath(cwd)

assert read.out == docker_compose_bin + " -f ../project/docker-compose.yml " \
"run --rm " \
f"--volume={real_cwd}:/app " \
"--workdir=/app " \
"service\n"
assert read.out == docker_compose_bin + \
" -f " + \
os.path.join("..", "project", "docker-compose.yml") + \
" " \
"run --rm " \
f"--volume={real_cwd}:/app " \
"--workdir=/app " \
"service\n"

def test_run_docker_binary_exe(self, project_loader, capsys: CaptureFixture):
project_loader("exe")
Expand Down
4 changes: 4 additions & 0 deletions tests/feature/ytt/test_ytt.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os

import pytest

from ddb.__main__ import load_registered_features, register_actions_in_event_bus
from ddb.config import migrations, config
from ddb.config.migrations import PropertyMigration
Expand All @@ -9,6 +11,7 @@
from ddb.feature.ytt import YttFeature


@pytest.mark.skipif("os.name == 'nt'")
class TestYttAction:
def test_empty_project_without_core(self, project_loader):
project_loader("empty")
Expand Down Expand Up @@ -118,6 +121,7 @@ def test_depends_suffixes(self, project_loader):
assert rendered == expected


@pytest.mark.skipif("os.name == 'nt'")
class TestYttAutofix:
def teardown_method(self, test_method):
migrations.set_history()
Expand Down
Loading

0 comments on commit 2aded1f

Please sign in to comment.