Commit 198c2d6a authored by Carlos Ramos Carreño's avatar Carlos Ramos Carreño

Accept `pathlib.Path` objects as a software URL.

parent f4ceaaa3
...@@ -26,141 +26,177 @@ ...@@ -26,141 +26,177 @@
# #
############################################################################## ##############################################################################
from __future__ import annotations
import fnmatch import fnmatch
import glob import glob
import os import os
import re import re
import sys
import warnings import warnings
import pkg_resources import pkg_resources
import requests import requests
from six.moves.configparser import ConfigParser from configparser import ConfigParser
import six
try: import subprocess
import subprocess32 as subprocess
except ImportError:
import subprocess # type: ignore
subprocess # pyflakes
try: from typing import Collection, Dict, Iterable, List, Mapping, Sequence, TYPE_CHECKING
from typing import Dict, Iterable, List
except ImportError:
pass
from ..slap.standalone import StandaloneSlapOS from ..slap.standalone import StandaloneSlapOS
from ..grid.utils import md5digest from ..grid.utils import md5digest
if TYPE_CHECKING:
if sys.version_info >= (3, 8):
from typing import TypedDict
else:
from typing_extensions import TypedDict
class SafetyDBEntry(TypedDict):
advisory: str
cve: str
id: str
more_info_path: str
specs: Sequence[str]
v: str
def checkSoftware(slap, software_url):
# type: (StandaloneSlapOS, str) -> None def checkSoftware(
"""Check software installation. slap: StandaloneSlapOS,
software_url: str | os.PathLike,
) -> None:
"""
Check software installation.
This perform a few basic static checks for common problems This perform a few basic static checks for common problems
with software installations. with software installations.
Args:
slap: The SlapOS system.
software_url: URL or path to the software to install.
""" """
# Check that all components set rpath correctly and we don't have miss linking any libraries. # Check that all components set rpath correctly and we don't have miss
# Also check that they are not linked against system libraries, except a list of core # linking any libraries.
# system libraries. # Also check that they are not linked against system libraries, except a
system_lib_allowed_list = set(( # list of core system libraries.
'libanl', system_lib_allowed_list = {
'libc', "libanl",
'libcrypt', "libc",
'libdl', "libcrypt",
'libgcc_s', "libdl",
'libgomp', "libgcc_s",
'libm', "libgomp",
'libmvec', "libm",
'libnsl', "libmvec",
'libpthread', "libnsl",
'libthread_db', "libpthread",
'libresolv', "libthread_db",
'librt', "libresolv",
'libstdc++', "librt",
'libquadmath', "libstdc++",
'libutil', "libquadmath",
)) "libutil",
}
# Some libraries might also not be found statically but provided at run time. # Some libraries might also not be found statically but provided at run time.
missing_lib_allowed_list = set(( missing_lib_allowed_list = {
# references to liblibgolang.so from projects outside of pygolang are resolved at runtime for now. # references to liblibgolang.so from projects outside of pygolang are
# https://github.com/mdavidsaver/setuptools_dso/issues/11#issuecomment-808258994 # resolved at runtime for now.
'liblibgolang', # https://github.com/mdavidsaver/setuptools_dso/issues/11#issuecomment-808258994
)) "liblibgolang",
}
# we also ignore a few patterns for part that are known to be binary distributions,
# for which we generate LD_LIBRARY_PATH wrappers or we don't use directly. # we also ignore a few patterns for part that are known to be binary
ignored_file_patterns = set(( # distributions, for which we generate LD_LIBRARY_PATH wrappers or we don't
'*/parts/java-re*/*', # use directly.
'*/parts/firefox*/*', ignored_file_patterns = {
'*/parts/chromium-*/*', "*/parts/java-re*/*",
'*/parts/chromedriver*/*', "*/parts/firefox*/*",
'*/parts/libreoffice-bin/*', "*/parts/chromium-*/*",
'*/parts/wkhtmltopdf/*', "*/parts/chromedriver*/*",
# R uses wrappers to relocate symbols "*/parts/libreoffice-bin/*",
'*/r-language/lib*/R/*', "*/parts/wkhtmltopdf/*",
# pulp is a git checkout with some executables # R uses wrappers to relocate symbols
'*/pulp-repository.git/src/pulp/solverdir/cbc*', "*/r-language/lib*/R/*",
# nss is not a binary distribution, but for some reason it has invalid rpath, but it does # pulp is a git checkout with some executables
# not seem to be a problem in our use cases. "*/pulp-repository.git/src/pulp/solverdir/cbc*",
'*/parts/nss/*', # nss is not a binary distribution, but for some reason it has invalid
# npm packages containing binaries # rpath, but it does not seem to be a problem in our use cases.
'*/node_modules/phantomjs*/*', "*/parts/nss/*",
'*/grafana/tools/phantomjs/*', # npm packages containing binaries
'*/node_modules/puppeteer/*', "*/node_modules/phantomjs*/*",
# left over of compilation failures "*/grafana/tools/phantomjs/*",
'*/*__compile__/*', "*/node_modules/puppeteer/*",
# build dir for packages built in-place # left over of compilation failures
'*/parts/wendelin.core/build/*', "*/*__compile__/*",
# the depot_tools package used to build Chromium installs some # build dir for packages built in-place
# Python libraries lacking an rpath; these are not actually used "*/parts/wendelin.core/build/*",
# by Chromium itself # the depot_tools package used to build Chromium installs some
'*/.vpython-root/*', # Python libraries lacking an rpath; these are not actually used
# this library is not readable by group/other # by Chromium itself
'*/libexec/ssh-keysign', "*/.vpython-root/*",
# this library is just a test loading foo.so which doesn't exist # this library is not readable by group/other
'*/test/ELF/Inputs/version-use.so', "*/libexec/ssh-keysign",
# this library is just a text file containing "GROUP( libtinfo.so )" so it is not a dynamic executable # this library is just a test loading foo.so which doesn't exist
'*/lib/libtermcap.so', "*/test/ELF/Inputs/version-use.so",
# this binary is not compiled but downloaded directly from internet (and we use a wrapper with LD_LIBRARY_PATH to use it) # this library is just a text file containing "GROUP( libtinfo.so )" so
'*/bin/phantomjs', # it is not a dynamic executable
)) "*/lib/libtermcap.so",
# this binary is not compiled but downloaded directly from internet (and
software_hash = md5digest(software_url) # we use a wrapper with LD_LIBRARY_PATH to use it)
error_list = [] "*/bin/phantomjs",
warning_list = [] }
software_hash = md5digest(os.fspath(software_url))
error_list: List[str] = []
warning_list: List[str] = []
ldd_so_resolved_re = re.compile( ldd_so_resolved_re = re.compile(
r'\t(?P<library_name>.*) => (?P<library_path>.*) \(0x') r"\t(?P<library_name>.*) => (?P<library_path>.*) \(0x",
ldd_already_loaded_re = re.compile(r'\t(?P<library_name>.*) \(0x') )
warning_execution_permission_re = re.compile(r'ldd: warning: you do not have execution permission for (?P<library_path>.*)') ldd_already_loaded_re = re.compile(r"\t(?P<library_name>.*) \(0x")
ldd_not_found_re = re.compile(r'\t(?P<library_name>.*) => not found.*') warning_execution_permission_re = re.compile(
r"ldd: warning: you do not have execution permission for (?P<library_path>.*)",
)
ldd_not_found_re = re.compile(r"\t(?P<library_name>.*) => not found.*")
class DynamicLibraryNotFound(Exception): class DynamicLibraryNotFound(Exception):
"""Exception raised when ldd cannot resolve a library. """Exception raised when ldd cannot resolve a library."""
def getLddOutput(path: str) -> Mapping[str, str]:
""" """
def getLddOutput(path): Get the output of ldd.
# type: (str) -> Dict[str, str]
"""Parse ldd output on shared object/executable as `path` and returns a mapping Parse ldd output on shared object/executable as ``path`` and returns a
of library paths or None when library is not found, keyed by library so name. mapping of library paths or None when library is not found, keyed by
library so name.
Raises a `DynamicLibraryNotFound` if any dynamic library is not found. Raises a `DynamicLibraryNotFound` if any dynamic library is not found.
Special entries, like VDSO ( linux-vdso.so.1 ) or ELF interpreter Special entries, like VDSO ( linux-vdso.so.1 ) or ELF interpreter
( /lib64/ld-linux-x86-64.so.2 ) are ignored. ( /lib64/ld-linux-x86-64.so.2 ) are ignored.
Args:
path: Path to shared object passed to ldd.
Returns:
A mapping of library so name to its path (or to `Ǹone`` if it is not
found).
""" """
libraries = {} # type: Dict[str, str] libraries: Dict[str, str] = {}
try: try:
ldd_output = subprocess.check_output( ldd_output = subprocess.check_output(
('ldd', path), ("ldd", path),
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
universal_newlines=True, universal_newlines=True,
) )
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
if e.output not in ('\tnot a dynamic executable\n',): if e.output not in ("\tnot a dynamic executable\n",):
raise raise
return libraries return libraries
if ldd_output == '\tstatically linked\n': if ldd_output == "\tstatically linked\n":
return libraries return libraries
not_found = [] not_found = []
...@@ -170,32 +206,45 @@ def checkSoftware(slap, software_url): ...@@ -170,32 +206,45 @@ def checkSoftware(slap, software_url):
warning_execution_permission_match = warning_execution_permission_re.match(line) warning_execution_permission_match = warning_execution_permission_re.match(line)
not_found_match = ldd_not_found_re.match(line) not_found_match = ldd_not_found_re.match(line)
if resolved_so_match: if resolved_so_match:
libraries[resolved_so_match.group( libraries[resolved_so_match.group("library_name")] = resolved_so_match.group(
'library_name')] = resolved_so_match.group('library_path') "library_path"
)
elif ldd_already_loaded_match: elif ldd_already_loaded_match:
# VDSO or ELF, ignore . See https://stackoverflow.com/a/35805410/7294664 for more about this # VDSO or ELF, ignore .
# See https://stackoverflow.com/a/35805410/7294664 for more about this
pass pass
elif warning_execution_permission_match: elif warning_execution_permission_match:
# for now, ignore permission warnings # for now, ignore permission warnings
pass pass
elif not_found_match: elif not_found_match:
library_name = not_found_match.group('library_name') library_name = not_found_match.group("library_name")
if library_name.split('.')[0] not in missing_lib_allowed_list: if library_name.split(".")[0] not in missing_lib_allowed_list:
not_found.append(line) not_found.append(line)
else: else:
raise RuntimeError('Unknown ldd line %s for %s.' % (line, path)) raise RuntimeError(f"Unknown ldd line {line} for {path}.")
if not_found: if not_found:
not_found_text = '\n'.join(not_found) not_found_text = "\n".join(not_found)
raise DynamicLibraryNotFound( raise DynamicLibraryNotFound(
'{path} has some not found libraries:\n{not_found_text}'.format( f"{path} has some not found libraries:\n{not_found_text}",
**locals())) )
return libraries return libraries
def checkExecutableLink(paths_to_check, valid_paths_for_libs): def checkExecutableLink(
# type: (Iterable[str], Iterable[str]) -> List[str] paths_to_check: Collection[str],
"""Check shared libraries linked with executables in `paths_to_check`. valid_paths_for_libs: Collection[str],
) -> Sequence[str]:
"""
Check shared libraries linked with executables in `paths_to_check`.
Only libraries from `valid_paths_for_libs` are accepted. Only libraries from `valid_paths_for_libs` are accepted.
Returns a list of error messages.
Args:
paths_to_check: Paths to executables to check.
valid_paths_for_libs: Paths to libraries that can be linked.
Returns:
A list of error messages.
""" """
valid_paths_for_libs = [os.path.realpath(x) for x in valid_paths_for_libs] valid_paths_for_libs = [os.path.realpath(x) for x in valid_paths_for_libs]
executable_link_error_list = [] executable_link_error_list = []
...@@ -203,82 +252,97 @@ def checkSoftware(slap, software_url): ...@@ -203,82 +252,97 @@ def checkSoftware(slap, software_url):
for root, dirs, files in os.walk(path): for root, dirs, files in os.walk(path):
for f in files: for f in files:
f = os.path.join(root, f) f = os.path.join(root, f)
if any(fnmatch.fnmatch(f, ignored_pattern) if any(
for ignored_pattern in ignored_file_patterns): fnmatch.fnmatch(f, ignored_pattern)
for ignored_pattern in ignored_file_patterns
):
continue continue
if os.access(f, os.X_OK) or fnmatch.fnmatch(f, '*.so'): if os.access(f, os.X_OK) or fnmatch.fnmatch(f, "*.so"):
try: try:
libs = getLddOutput(f) libs = getLddOutput(f)
except DynamicLibraryNotFound as e: except DynamicLibraryNotFound as e:
executable_link_error_list.append(str(e)) executable_link_error_list.append(str(e))
else: else:
for lib, lib_path in libs.items(): for lib, lib_path in libs.items():
if lib.split('.')[0] in system_lib_allowed_list: if lib.split(".")[0] in system_lib_allowed_list:
continue continue
lib_path = os.path.realpath(lib_path) lib_path = os.path.realpath(lib_path)
# dynamically linked programs can only be linked with libraries # dynamically linked programs can only be linked with libraries
# present in software or in shared parts repository. # present in software or in shared parts repository.
if any(lib_path.startswith(valid_path) if any(
for valid_path in valid_paths_for_libs): lib_path.startswith(valid_path) for valid_path in valid_paths_for_libs
):
continue continue
executable_link_error_list.append( executable_link_error_list.append(
'{f} uses system library {lib_path} for {lib}'.format( f"{f} uses system library {lib_path} for {lib}",
**locals())) )
return executable_link_error_list return executable_link_error_list
software_directory = os.path.join(slap.software_directory, software_hash) software_directory = os.path.join(slap.software_directory, software_hash)
paths_to_check = set((software_directory, )) paths_to_check = {software_directory}
# Compute the paths to check by inspecting buildout installed database # Compute the paths to check by inspecting buildout installed database
# for this software. We are looking for shared parts installed by recipes. # for this software. We are looking for shared parts installed by recipes.
config_parser = ConfigParser() config_parser = ConfigParser()
config_parser.read(os.path.join(software_directory, '.installed.cfg')) config_parser.read(os.path.join(software_directory, ".installed.cfg"))
for section_name in config_parser.sections(): for section_name in config_parser.sections():
for option_name in 'location', '__buildout_installed__': for option_name in "location", "__buildout_installed__":
if config_parser.has_option(section_name, option_name): if config_parser.has_option(section_name, option_name):
for section_path in config_parser.get(section_name, option_name).splitlines(): for section_path in config_parser.get(section_name, option_name).splitlines():
if section_path and not section_path.startswith(software_directory): if section_path and not section_path.startswith(software_directory):
paths_to_check.add(section_path) paths_to_check.add(section_path)
error_list.extend( error_list.extend(
checkExecutableLink( checkExecutableLink(
paths_to_check, paths_to_check,
tuple(paths_to_check) + tuple(slap._shared_part_list), tuple(paths_to_check) + tuple(slap._shared_part_list),
)) )
)
# check this software is not referenced in any shared parts. # check this software is not referenced in any shared parts.
for signature_file in glob.glob( for signature_file in glob.glob(
os.path.join( os.path.join(
slap.shared_directory, slap.shared_directory,
'*', "*",
'*', "*",
'.buildout-shared.json', ".buildout-shared.json",
)): )
):
with open(signature_file) as f: with open(signature_file) as f:
signature_content = f.read() signature_content = f.read()
if software_hash in signature_content: if software_hash in signature_content:
error_list.append( error_list.append(
"Shared part is referencing non shared part or software {}\n{}\n".format( f"Shared part is referencing non shared part or "
signature_file, signature_content)) f"software {signature_file}\n{signature_content}\n",
)
def checkEggsVersionsKnownVulnerabilities( def checkEggsVersionsKnownVulnerabilities(
egg_directories, egg_directories: Sequence[str],
safety_db=requests.get( safety_db: Mapping[str, Sequence[SafetyDBEntry]] = requests.get(
'https://raw.githubusercontent.com/pyupio/safety-db/master/data/insecure_full.json' "https://raw.githubusercontent.com/pyupio/safety-db/master/data/insecure_full.json"
).json()): ).json(),
# type: (List[str], Dict) -> Iterable[str] ) -> Iterable[str]:
"""Check eggs against known vulnerabilities database from https://github.com/pyupio/safety-db
""" """
def get_python_versions(): Check eggs against known vulnerabilities.
# () -> Iterable[str]
"""Returns all python versions used in egg_directories Args:
""" egg_directories: Paths to the directories containing Python eggs.
safety_db: The database of known vulnerabilities. By default it uses the
database from https://github.com/pyupio/safety-db.
Yields:
A description of each vulnerability that applies to the current eggs.
"""
def get_python_versions() -> Iterable[str]:
"""Returns all python versions used in egg_directories."""
python_versions = set() python_versions = set()
for egg_dir in egg_directories: for egg_dir in egg_directories:
basename, _ = os.path.splitext(os.path.basename(egg_dir)) basename, _ = os.path.splitext(os.path.basename(egg_dir))
match = pkg_resources.EGG_NAME(basename) match = pkg_resources.EGG_NAME(basename)
if match: if match:
pyver = match.group('pyver') pyver = match.group("pyver")
if pyver: if pyver:
python_versions.add(pyver) python_versions.add(pyver)
return python_versions return python_versions
...@@ -290,38 +354,42 @@ def checkSoftware(slap, software_url): ...@@ -290,38 +354,42 @@ def checkSoftware(slap, software_url):
if known_vulnerabilities: if known_vulnerabilities:
for distribution in env[egg]: for distribution in env[egg]:
for known_vulnerability in known_vulnerabilities: for known_vulnerability in known_vulnerabilities:
for vulnerable_spec in known_vulnerability['specs']: for vulnerable_spec in known_vulnerability["specs"]:
for req in pkg_resources.parse_requirements(egg + for req in pkg_resources.parse_requirements(
vulnerable_spec): egg + vulnerable_spec,
):
vulnerability_description = "\n".join( vulnerability_description = "\n".join(
u"{}: {}".format(*item) f"{key}: {value}" for key, value in known_vulnerability.items()
for item in known_vulnerability.items()) )
if distribution in req: if distribution in req:
yield ( yield (
u"{egg} use vulnerable version {distribution.version} because {vulnerable_spec}.\n" f"{egg} use vulnerable version {distribution.version} "
"{vulnerability_description}\n".format(**locals())) f"because {vulnerable_spec}.\n"
f"{vulnerability_description}\n"
)
warning_list.extend( warning_list.extend(
checkEggsVersionsKnownVulnerabilities( checkEggsVersionsKnownVulnerabilities(
glob.glob( glob.glob(
os.path.join( os.path.join(
slap.software_directory, slap.software_directory,
software_hash, software_hash,
'eggs', "eggs",
'*', "*",
)) + glob.glob( )
os.path.join( )
slap.software_directory, + glob.glob(
software_hash, os.path.join(
'develop-eggs', slap.software_directory,
'*', software_hash,
)))) "develop-eggs",
"*",
)
)
)
)
if warning_list: if warning_list:
if six.PY2: warnings.warn("\n".join(warning_list))
# https://bugs.python.org/issue34752
warnings.warn('\n'.join(warning_list).encode('utf-8'))
else:
warnings.warn('\n'.join(warning_list))
if error_list: if error_list:
raise RuntimeError('\n'.join(error_list)) raise RuntimeError("\n".join(error_list))
...@@ -37,8 +37,6 @@ import sqlite3 ...@@ -37,8 +37,6 @@ import sqlite3
import unittest import unittest
import warnings import warnings
from six.moves.urllib.parse import urlparse
from netaddr import valid_ipv6 from netaddr import valid_ipv6
from .utils import getPortFromPath from .utils import getPortFromPath
...@@ -54,6 +52,7 @@ from .check_software import checkSoftware ...@@ -54,6 +52,7 @@ from .check_software import checkSoftware
from ..proxy.db_version import DB_VERSION from ..proxy.db_version import DB_VERSION
from os import fspath, PathLike
from typing import ( from typing import (
Callable, Callable,
ClassVar, ClassVar,
...@@ -67,6 +66,8 @@ from typing import ( ...@@ -67,6 +66,8 @@ from typing import (
Type, Type,
TypeVar, TypeVar,
) )
from urllib.parse import urlparse
ManagedResourceType = TypeVar("ManagedResourceType", bound=ManagedResource) ManagedResourceType = TypeVar("ManagedResourceType", bound=ManagedResource)
IPV4_ADDRESS_DEFAULT: str = os.environ['SLAPOS_TEST_IPV4'] IPV4_ADDRESS_DEFAULT: str = os.environ['SLAPOS_TEST_IPV4']
...@@ -95,7 +96,7 @@ SNAPSHOT_DIRECTORY_DEFAULT: str | None = os.environ.get( ...@@ -95,7 +96,7 @@ SNAPSHOT_DIRECTORY_DEFAULT: str | None = os.environ.get(
) )
def makeModuleSetUpAndTestCaseClass( def makeModuleSetUpAndTestCaseClass(
software_url: str, software_url: str | PathLike[str],
*, *,
base_directory: str | None = None, base_directory: str | None = None,
ipv4_address: str = IPV4_ADDRESS_DEFAULT, ipv4_address: str = IPV4_ADDRESS_DEFAULT,
...@@ -125,7 +126,7 @@ def makeModuleSetUpAndTestCaseClass( ...@@ -125,7 +126,7 @@ def makeModuleSetUpAndTestCaseClass(
See https://lab.nexedi.com/kirr/slapns for a solution to this problem. See https://lab.nexedi.com/kirr/slapns for a solution to this problem.
Args: Args:
software_url: The URL of the software to test. software_url: The URL or path of the software to test.
base_directory: The base directory used for SlapOS. base_directory: The base directory used for SlapOS.
By default, it will use the value in the environment variable By default, it will use the value in the environment variable
``SLAPOS_TEST_WORKING_DIR``. ``SLAPOS_TEST_WORKING_DIR``.
...@@ -189,7 +190,7 @@ def makeModuleSetUpAndTestCaseClass( ...@@ -189,7 +190,7 @@ def makeModuleSetUpAndTestCaseClass(
) )
if not software_id: if not software_id:
software_id = urlparse(software_url).path.split('/')[-2] software_id = urlparse(fspath(software_url)).path.split('/')[-2]
logging.basicConfig( logging.basicConfig(
level=logging.DEBUG, level=logging.DEBUG,
...@@ -253,31 +254,40 @@ def makeModuleSetUpAndTestCaseClass( ...@@ -253,31 +254,40 @@ def makeModuleSetUpAndTestCaseClass(
return setUpModule, SlapOSInstanceTestCase_ return setUpModule, SlapOSInstanceTestCase_
def installSoftwareUrlList(cls, software_url_list, max_retry=10, debug=False): def installSoftwareUrlList(
# type: (Type[SlapOSInstanceTestCase], Iterable[str], int, bool) -> None cls: Type[SlapOSInstanceTestCase],
"""Install softwares on the current testing slapos, for use in `setUpModule`. software_url_list: Sequence[str | PathLike[str]],
max_retry: int = 10,
debug: bool = False,
) -> None:
"""
Install softwares on the current testing slapos, for use in `setUpModule`.
This also check softwares with `checkSoftware`.
Args:
cls: The test case class used for the installation.
software_url_list: List of URLs or paths to install.
max_retry: Number of times that the installation will be retried if there
is an error.
debug: If set to ``True`` the software will not be automatically removed
if there is an error during the installation process, in order to
facilitate inspection during debug.
This also check softwares with `checkSoftware`
""" """
def _storeSoftwareSnapshot(name): def _storeSoftwareSnapshot(name: str) -> None:
for path in glob.glob(os.path.join( for path in glob.glob(os.path.join(
cls._base_directory, cls._base_directory,
'var', "var/log/*",
'log',
'*',
)) + glob.glob(os.path.join( )) + glob.glob(os.path.join(
cls.slap.software_directory, cls.slap.software_directory,
'*', "*/*.cfg",
'*.cfg',
)) + glob.glob(os.path.join( )) + glob.glob(os.path.join(
cls.slap.software_directory, cls.slap.software_directory,
'*', "*/.installed.cfg",
'.installed.cfg',
)) + glob.glob(os.path.join( )) + glob.glob(os.path.join(
cls.slap.shared_directory, cls.slap.shared_directory,
'*', "*/*/.slapos.recipe.cmmi.signature",
'*',
'.slapos.recipe.cmmi.signature',
)): )):
cls._copySnapshot(path, name) cls._copySnapshot(path, name)
...@@ -286,7 +296,7 @@ def installSoftwareUrlList(cls, software_url_list, max_retry=10, debug=False): ...@@ -286,7 +296,7 @@ def installSoftwareUrlList(cls, software_url_list, max_retry=10, debug=False):
cls.slap.start() cls.slap.start()
for software_url in software_url_list: for software_url in software_url_list:
cls.logger.debug("Supplying %s", software_url) cls.logger.debug("Supplying %s", software_url)
cls.slap.supply(software_url) cls.slap.supply(fspath(software_url))
cls.logger.debug("Waiting for slapos node software to build") cls.logger.debug("Waiting for slapos node software to build")
cls.slap.waitForSoftware(max_retry=max_retry, debug=debug, install_all=not cls._skip_software_rebuild) cls.slap.waitForSoftware(max_retry=max_retry, debug=debug, install_all=not cls._skip_software_rebuild)
_storeSoftwareSnapshot('setupModule') _storeSoftwareSnapshot('setupModule')
...@@ -305,7 +315,7 @@ def installSoftwareUrlList(cls, software_url_list, max_retry=10, debug=False): ...@@ -305,7 +315,7 @@ def installSoftwareUrlList(cls, software_url_list, max_retry=10, debug=False):
try: try:
for software_url in software_url_list: for software_url in software_url_list:
cls.logger.debug("Removing %s", software_url) cls.logger.debug("Removing %s", software_url)
cls.slap.supply(software_url, state="destroyed") cls.slap.supply(fspath(software_url), state="destroyed")
cls.logger.debug("Waiting for slapos node software to remove") cls.logger.debug("Waiting for slapos node software to remove")
cls.slap.waitForSoftware(max_retry=max_retry, debug=debug) cls.slap.waitForSoftware(max_retry=max_retry, debug=debug)
except BaseException: except BaseException:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment