snakemake-interface-software-deployment-plugins 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snakemake_interface_software_deployment_plugins-0.1.0/LICENSE +21 -0
- snakemake_interface_software_deployment_plugins-0.1.0/PKG-INFO +21 -0
- snakemake_interface_software_deployment_plugins-0.1.0/README.md +3 -0
- snakemake_interface_software_deployment_plugins-0.1.0/pyproject.toml +29 -0
- snakemake_interface_software_deployment_plugins-0.1.0/snakemake_interface_software_deployment_plugins/__init__.py +177 -0
- snakemake_interface_software_deployment_plugins-0.1.0/snakemake_interface_software_deployment_plugins/_common.py +10 -0
- snakemake_interface_software_deployment_plugins-0.1.0/snakemake_interface_software_deployment_plugins/registry/__init__.py +51 -0
- snakemake_interface_software_deployment_plugins-0.1.0/snakemake_interface_software_deployment_plugins/registry/plugin.py +46 -0
- snakemake_interface_software_deployment_plugins-0.1.0/snakemake_interface_software_deployment_plugins/settings.py +17 -0
- snakemake_interface_software_deployment_plugins-0.1.0/snakemake_interface_software_deployment_plugins/tests.py +95 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Snakemake Community
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: snakemake-interface-software-deployment-plugins
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: This package provides a stable interface for interactions between Snakemake and its software deployment plugins.
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Johannes Köster
|
|
7
|
+
Author-email: johannes.koester@uni-due.de
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Dist: argparse-dataclass (>=2.0.0,<3.0.0)
|
|
15
|
+
Requires-Dist: snakemake-interface-common (>=1.17.4,<2.0.0)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# Stable interfaces and functionality for Snakemake software deployment plugins
|
|
19
|
+
|
|
20
|
+
This package provides a stable interface for interactions between Snakemake and its software deployment plugins.
|
|
21
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
authors = ["Johannes Köster <johannes.koester@uni-due.de>"]
|
|
3
|
+
description = "This package provides a stable interface for interactions between Snakemake and its software deployment plugins."
|
|
4
|
+
license = "MIT"
|
|
5
|
+
name = "snakemake-interface-software-deployment-plugins"
|
|
6
|
+
packages = [{include = "snakemake_interface_software_deployment_plugins"}]
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
version = "0.1.0"
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
argparse-dataclass = "^2.0.0"
|
|
12
|
+
python = "^3.11"
|
|
13
|
+
snakemake-interface-common = "^1.17.4"
|
|
14
|
+
|
|
15
|
+
[tool.poetry.group.dev.dependencies]
|
|
16
|
+
coverage = {extras = ["toml"], version = "^6.3.1"}
|
|
17
|
+
flake8-bugbear = "^22.1.11"
|
|
18
|
+
pytest = "^7.0"
|
|
19
|
+
ruff = "^0.9.9"
|
|
20
|
+
|
|
21
|
+
[tool.coverage.run]
|
|
22
|
+
omit = [".*", "*/site-packages/*"]
|
|
23
|
+
|
|
24
|
+
[tool.coverage.report]
|
|
25
|
+
fail_under = 63
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
build-backend = "poetry.core.masonry.api"
|
|
29
|
+
requires = ["poetry-core"]
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
__author__ = "Johannes Köster"
|
|
2
|
+
__copyright__ = "Copyright 2024, Johannes Köster"
|
|
3
|
+
__email__ = "johannes.koester@uni-due.de"
|
|
4
|
+
__license__ = "MIT"
|
|
5
|
+
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from dataclasses import dataclass, field, fields
|
|
8
|
+
import hashlib
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import sys
|
|
11
|
+
from typing import Optional
|
|
12
|
+
import subprocess as sp
|
|
13
|
+
|
|
14
|
+
from snakemake_interface_software_deployment_plugins.settings import (
|
|
15
|
+
SoftwareDeploymentSettingsBase,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_MANAGED_FIELDS = {
|
|
20
|
+
"settings",
|
|
21
|
+
"_managed_hash_store",
|
|
22
|
+
"_managed_deployment_hash_store",
|
|
23
|
+
"_obj_hash",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class EnvSpecBase(ABC):
|
|
29
|
+
within: Optional["EnvSpecBase"]
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def env_cls(cls):
|
|
33
|
+
return sys.modules[__name__].EnvBase
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class EnvBase:
|
|
38
|
+
spec: EnvSpecBase
|
|
39
|
+
within: Optional["EnvBase"]
|
|
40
|
+
settings: Optional[SoftwareDeploymentSettingsBase]
|
|
41
|
+
_managed_hash_store: Optional[str] = field(init=False, default=None)
|
|
42
|
+
_managed_deployment_hash_store: Optional[str] = field(init=False, default=None)
|
|
43
|
+
_obj_hash: Optional[int] = field(init=False, default=None)
|
|
44
|
+
|
|
45
|
+
def __post_init__(self) -> None: # noqa B027
|
|
46
|
+
"""Do stuff after object initialization."""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def decorate_shellcmd(self, cmd: str) -> str:
|
|
51
|
+
"""Decorate given shell command such that it runs within the environment,
|
|
52
|
+
using self.spec.
|
|
53
|
+
"""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def record_hash(self, hash_object) -> None:
|
|
58
|
+
"""Update given hash object such that it changes whenever the environment
|
|
59
|
+
specified via self.spec could potentially contain a different set of
|
|
60
|
+
software (in terms of versions or packages).
|
|
61
|
+
"""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
def run_cmd(self, cmd: str, **kwargs) -> sp.CompletedProcess:
|
|
65
|
+
"""Run a command while potentially respecting the self.within environment,
|
|
66
|
+
returning the result of subprocess.run.
|
|
67
|
+
|
|
68
|
+
kwargs is passed to subprocess.run, shell=True is always set.
|
|
69
|
+
"""
|
|
70
|
+
if self.within is not None:
|
|
71
|
+
cmd = self.within.managed_decorate_shellcmd(cmd)
|
|
72
|
+
return sp.run(cmd, shell=True, **kwargs)
|
|
73
|
+
|
|
74
|
+
def managed_decorate_shellcmd(self, cmd: str) -> str:
|
|
75
|
+
cmd = self.decorate_shellcmd(cmd)
|
|
76
|
+
if self.within is not None:
|
|
77
|
+
cmd = self.within.managed_decorate_shellcmd(cmd)
|
|
78
|
+
return cmd
|
|
79
|
+
|
|
80
|
+
def hash(self) -> str:
|
|
81
|
+
return self._managed_generic_hash("hash")
|
|
82
|
+
|
|
83
|
+
def _managed_generic_hash(self, kind: str) -> str:
|
|
84
|
+
store = getattr(self, f"_managed_{kind}_store")
|
|
85
|
+
if store is None:
|
|
86
|
+
record_hash = f"record_{kind}"
|
|
87
|
+
hash_object = hashlib.md5()
|
|
88
|
+
if self.within is not None:
|
|
89
|
+
getattr(self.within, record_hash)(hash_object)
|
|
90
|
+
getattr(self, record_hash)(hash_object)
|
|
91
|
+
store = hash_object.hexdigest()
|
|
92
|
+
return store
|
|
93
|
+
|
|
94
|
+
def __hash__(self) -> int:
|
|
95
|
+
# take the hash of all fields by settings, _managed_hash_store and _managed_deployment_hash_store
|
|
96
|
+
if self._obj_hash is None:
|
|
97
|
+
self._obj_hash = hash(
|
|
98
|
+
tuple(
|
|
99
|
+
getattr(self, field.name)
|
|
100
|
+
for field in fields(self)
|
|
101
|
+
if field.name not in _MANAGED_FIELDS
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
return self._obj_hash
|
|
105
|
+
|
|
106
|
+
def __eq__(self, other) -> bool:
|
|
107
|
+
return self.__class__ == other.__class__ and all(
|
|
108
|
+
getattr(self, field.name) == getattr(other, field.name)
|
|
109
|
+
for field in fields(self)
|
|
110
|
+
if field.name not in _MANAGED_FIELDS
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class DeployableEnvBase(EnvBase):
|
|
116
|
+
_deployment_prefix: Optional[Path] = None
|
|
117
|
+
|
|
118
|
+
@abstractmethod
|
|
119
|
+
def deploy(self) -> None:
|
|
120
|
+
"""Deploy the environment to self.deployment_path.
|
|
121
|
+
|
|
122
|
+
When issuing shell commands, the environment should use
|
|
123
|
+
self.provider.run(cmd: str) in order to ensure that it runs within eventual
|
|
124
|
+
parent environments (e.g. a container or an env module).
|
|
125
|
+
"""
|
|
126
|
+
...
|
|
127
|
+
|
|
128
|
+
@abstractmethod
|
|
129
|
+
def record_deployment_hash(self, hash_object) -> None:
|
|
130
|
+
"""Update given hash such that it changes whenever the environment
|
|
131
|
+
needs to be redeployed, e.g. because its content has changed or the
|
|
132
|
+
deployment location has changed. The latter is only relevant if the
|
|
133
|
+
deployment is senstivive to the path (e.g. in case of conda, which patches
|
|
134
|
+
the RPATH in binaries).
|
|
135
|
+
"""
|
|
136
|
+
...
|
|
137
|
+
|
|
138
|
+
@abstractmethod
|
|
139
|
+
def remove(self) -> None:
|
|
140
|
+
"""Remove the deployed environment."""
|
|
141
|
+
...
|
|
142
|
+
|
|
143
|
+
def managed_deploy(self) -> None:
|
|
144
|
+
if isinstance(self, ArchiveableEnvBase) and self.archive_path.exists():
|
|
145
|
+
self.deploy_from_archive()
|
|
146
|
+
else:
|
|
147
|
+
self.deploy()
|
|
148
|
+
|
|
149
|
+
def deployment_hash(self) -> str:
|
|
150
|
+
return self._managed_generic_hash("deployment_hash")
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def deployment_path(self) -> Path:
|
|
154
|
+
return self.provider.deployment_prefix / self.deployment_hash()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class ArchiveableEnvBase(EnvBase):
|
|
158
|
+
archive_prefix: Optional[Path] = None
|
|
159
|
+
|
|
160
|
+
@abstractmethod
|
|
161
|
+
def archive(self) -> None:
|
|
162
|
+
"""Archive the environment to self.archive_path.
|
|
163
|
+
|
|
164
|
+
When issuing shell commands, the environment should use
|
|
165
|
+
self.provider.run(cmd: str) in order to ensure that it runs within eventual
|
|
166
|
+
parent environments (e.g. a container or an env module).
|
|
167
|
+
"""
|
|
168
|
+
...
|
|
169
|
+
|
|
170
|
+
@abstractmethod
|
|
171
|
+
def deploy_from_archive(self) -> None:
|
|
172
|
+
"""Deploy the environment from an archive."""
|
|
173
|
+
...
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def archive_path(self) -> Path:
|
|
177
|
+
return self.archive_prefix / self.hash()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
__author__ = "Johannes Köster"
|
|
2
|
+
__copyright__ = "Copyright 2024, Johannes Köster"
|
|
3
|
+
__email__ = "johannes.koester@uni-due.de"
|
|
4
|
+
__license__ = "MIT"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
software_deployment_plugin_prefix = "snakemake-software-deployment-plugin-"
|
|
8
|
+
software_deployment_plugin_module_prefix = software_deployment_plugin_prefix.replace(
|
|
9
|
+
"-", "_"
|
|
10
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
__author__ = "Johannes Köster"
|
|
2
|
+
__copyright__ = "Copyright 2024, Johannes Köster"
|
|
3
|
+
__email__ = "johannes.koester@uni-due.de"
|
|
4
|
+
__license__ = "MIT"
|
|
5
|
+
|
|
6
|
+
import types
|
|
7
|
+
from typing import Mapping
|
|
8
|
+
from snakemake_interface_software_deployment_plugins.settings import (
|
|
9
|
+
SoftwareDeploymentSettingsBase,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from snakemake_interface_common.plugin_registry.attribute_types import (
|
|
13
|
+
AttributeKind,
|
|
14
|
+
AttributeMode,
|
|
15
|
+
AttributeType,
|
|
16
|
+
)
|
|
17
|
+
from snakemake_interface_software_deployment_plugins.registry.plugin import Plugin
|
|
18
|
+
from snakemake_interface_common.plugin_registry import PluginRegistryBase
|
|
19
|
+
from snakemake_interface_software_deployment_plugins import EnvBase, _common as common
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SoftwareDeploymentPluginRegistry(PluginRegistryBase):
|
|
23
|
+
"""This class is a singleton that holds all registered executor plugins."""
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def module_prefix(self) -> str:
|
|
27
|
+
return common.software_deployment_plugin_module_prefix
|
|
28
|
+
|
|
29
|
+
def load_plugin(self, name: str, module: types.ModuleType) -> Plugin:
|
|
30
|
+
"""Load a plugin by name."""
|
|
31
|
+
return Plugin(
|
|
32
|
+
_name=name,
|
|
33
|
+
_software_deployment_settings_cls=getattr(
|
|
34
|
+
module, "SoftwareDeploymentSettings", None
|
|
35
|
+
),
|
|
36
|
+
_env_cls=module.EnvBase,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def expected_attributes(self) -> Mapping[str, AttributeType]:
|
|
40
|
+
return {
|
|
41
|
+
"SoftwareDeploymentSettings": AttributeType(
|
|
42
|
+
cls=SoftwareDeploymentSettingsBase,
|
|
43
|
+
mode=AttributeMode.OPTIONAL,
|
|
44
|
+
kind=AttributeKind.CLASS,
|
|
45
|
+
),
|
|
46
|
+
"Env": AttributeType(
|
|
47
|
+
cls=EnvBase,
|
|
48
|
+
mode=AttributeMode.REQUIRED,
|
|
49
|
+
kind=AttributeKind.CLASS,
|
|
50
|
+
),
|
|
51
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
__author__ = "Johannes Köster"
|
|
2
|
+
__copyright__ = "Copyright 2024, Johannes Köster"
|
|
3
|
+
__email__ = "johannes.koester@uni-due.de"
|
|
4
|
+
__license__ = "MIT"
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Optional, Type
|
|
8
|
+
from snakemake_interface_software_deployment_plugins import EnvBase, EnvSpecBase
|
|
9
|
+
from snakemake_interface_software_deployment_plugins.settings import (
|
|
10
|
+
SoftwareDeploymentSettingsBase,
|
|
11
|
+
)
|
|
12
|
+
import snakemake_interface_software_deployment_plugins._common as common
|
|
13
|
+
|
|
14
|
+
from snakemake_interface_common.plugin_registry.plugin import PluginBase
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Plugin(PluginBase):
|
|
19
|
+
_software_deployment_settings_cls: Optional[Type[SoftwareDeploymentSettingsBase]]
|
|
20
|
+
_env_cls: Type[EnvBase]
|
|
21
|
+
_env_spec_cls: Type[EnvSpecBase]
|
|
22
|
+
_name: str
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def name(self):
|
|
26
|
+
return self._name
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def cli_prefix(self):
|
|
30
|
+
return "sdm-" + self._only_name
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def _only_name(self):
|
|
34
|
+
return self.name.replace(common.software_deployment_plugin_module_prefix, "")
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def settings_cls(self):
|
|
38
|
+
return self._software_deployment_settings_cls
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def env_cls(self):
|
|
42
|
+
return self._env_cls
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def env_spec_cls(self):
|
|
46
|
+
return self._env_spec_cls
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import snakemake_interface_common.plugin_registry.plugin
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class SoftwareDeploymentSettingsBase(
|
|
9
|
+
snakemake_interface_common.plugin_registry.plugin.SettingsBase
|
|
10
|
+
):
|
|
11
|
+
"""Base class for software deployment settings.
|
|
12
|
+
|
|
13
|
+
Software deployment plugins can define a subclass of this class,
|
|
14
|
+
named 'SoftwareDeploymentProviderSettings'.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
pass
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Optional, Type
|
|
3
|
+
import subprocess as sp
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from snakemake_interface_software_deployment_plugins import (
|
|
8
|
+
ArchiveableEnvBase,
|
|
9
|
+
DeployableEnvBase,
|
|
10
|
+
EnvBase,
|
|
11
|
+
EnvSpecBase,
|
|
12
|
+
)
|
|
13
|
+
from snakemake_interface_software_deployment_plugins.settings import (
|
|
14
|
+
SoftwareDeploymentSettingsBase,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_TEST_SDM_NAME = "test-sdm"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestSoftwareDeploymentBase(ABC):
|
|
22
|
+
__test__ = False
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def get_env_spec(self) -> EnvSpecBase:
|
|
26
|
+
"""
|
|
27
|
+
If the software deployment provider does not support deployable environments,
|
|
28
|
+
this method should return args for an existing environment spec that can be used
|
|
29
|
+
for testing
|
|
30
|
+
"""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def get_env_cls(self) -> Type[EnvBase]:
|
|
35
|
+
"""
|
|
36
|
+
Return the environment class that should be tested.
|
|
37
|
+
"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def get_test_cmd(self) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Return a test command that should be executed within the environment
|
|
44
|
+
with exit code 0 (i.e. without error).
|
|
45
|
+
"""
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
def get_software_deployment_provider_settings(
|
|
49
|
+
self,
|
|
50
|
+
) -> Optional[SoftwareDeploymentSettingsBase]:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def test_shellcmd(self, tmp_path):
|
|
54
|
+
env = self._get_env(tmp_path)
|
|
55
|
+
|
|
56
|
+
if isinstance(env, DeployableEnvBase):
|
|
57
|
+
pytest.skip("Environment is deployable, using test_deploy instead.")
|
|
58
|
+
|
|
59
|
+
cmd = self.get_test_cmd()
|
|
60
|
+
decorated_cmd = env.managed_decorate_shellcmd(cmd)
|
|
61
|
+
assert cmd != decorated_cmd
|
|
62
|
+
assert sp.run(decorated_cmd, shell=True).returncode == 0
|
|
63
|
+
|
|
64
|
+
def test_deploy(self, tmp_path):
|
|
65
|
+
env = self._get_env(tmp_path)
|
|
66
|
+
self._deploy(env, tmp_path)
|
|
67
|
+
cmd = env.managed_decorate_shellcmd(self.get_test_cmd())
|
|
68
|
+
assert sp.run(cmd, shell=True).returncode == 0
|
|
69
|
+
|
|
70
|
+
def test_archive(self, tmp_path):
|
|
71
|
+
env = self._get_env(tmp_path)
|
|
72
|
+
if not isinstance(env, ArchiveableEnvBase):
|
|
73
|
+
pytest.skip("Environment either not deployable or not archiveable.")
|
|
74
|
+
|
|
75
|
+
self._deploy(env, tmp_path)
|
|
76
|
+
|
|
77
|
+
env.archive()
|
|
78
|
+
assert any((tmp_path / "{_TEST_SDM_NAME}-archive").iterdir())
|
|
79
|
+
|
|
80
|
+
def _get_env(self, tmp_path) -> EnvBase:
|
|
81
|
+
env_cls = self.get_env_cls()
|
|
82
|
+
spec = self.get_env_spec()
|
|
83
|
+
args = {"settings": self.get_software_deployment_provider_settings()}
|
|
84
|
+
if issubclass(env_cls, DeployableEnvBase):
|
|
85
|
+
args["deployment_prefix"] = tmp_path / "deployments"
|
|
86
|
+
if issubclass(env_cls, ArchiveableEnvBase):
|
|
87
|
+
args["archive_prefix"] = tmp_path / "archives"
|
|
88
|
+
return env_cls(spec=spec, within=None, **args)
|
|
89
|
+
|
|
90
|
+
def _deploy(self, env: DeployableEnvBase, tmp_path):
|
|
91
|
+
if not isinstance(env, DeployableEnvBase):
|
|
92
|
+
pytest.skip("Environment is not deployable.")
|
|
93
|
+
|
|
94
|
+
env.deploy()
|
|
95
|
+
assert any((tmp_path / _TEST_SDM_NAME).iterdir())
|