You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

119 lines
3.5 KiB

  1. # -*- coding: utf-8 -*-
  2. from collections import OrderedDict
  3. from os.path import exists
  4. from subprocess import check_call
  5. from doodbalib import logger
  6. class Installer(object):
  7. """Base class to install packages with some package system."""
  8. _cleanup_commands = []
  9. _install_command = None
  10. _remove_command = None
  11. def __init__(self, file_path):
  12. self.file_path = file_path
  13. self._requirements = self.requirements()
  14. def _run_command(self, command):
  15. logger.info("Executing: %s", command)
  16. return check_call(command, shell=isinstance(command, str))
  17. def cleanup(self):
  18. """Remove cache and other garbage produced by the installer engine."""
  19. for command in self._cleanup_commands:
  20. self._run_command(command)
  21. def install(self):
  22. """Install the requirements from the given file."""
  23. if self._requirements:
  24. return not self._run_command(self._install_command + self._requirements)
  25. else:
  26. logger.info("No installable requirements found in %s", self.file_path)
  27. return False
  28. def remove(self):
  29. """Uninstall the requirements from the given file."""
  30. if not self._remove_command:
  31. return
  32. if self._requirements:
  33. self._run_command(self._remove_command + self._requirements)
  34. else:
  35. logger.info("No removable requirements found in %s", self.file_path)
  36. def requirements(self):
  37. """Get a list of requirements from the given file."""
  38. requirements = []
  39. try:
  40. with open(self.file_path, "r") as fh:
  41. for line in fh:
  42. line = line.strip()
  43. if not line or line.startswith("#"):
  44. continue
  45. requirements += line.split()
  46. except IOError:
  47. # No requirements file
  48. pass
  49. return requirements
  50. class AptInstaller(Installer):
  51. _cleanup_commands = [["apt-get", "-y", "autoremove"], "rm -Rf /var/lib/apt/lists/*"]
  52. _install_command = [
  53. "apt-get",
  54. "-o",
  55. "Dpkg::Options::=--force-confdef",
  56. "-o",
  57. "Dpkg::Options::=--force-confold",
  58. "-y",
  59. "--no-install-recommends",
  60. "install",
  61. ]
  62. _remove_command = ["apt-get", "purge", "-y"]
  63. def _dirty(self):
  64. return exists("/var/lib/apt/lists/lock")
  65. def cleanup(self):
  66. if self._dirty():
  67. super(AptInstaller, self).cleanup()
  68. def install(self):
  69. if not self._dirty() and self._requirements:
  70. self._run_command(["apt-get", "update"])
  71. return super(AptInstaller, self).install()
  72. class GemInstaller(Installer):
  73. _cleanup_commands = ["rm -Rf ~/.gem /var/lib/gems/*/cache/"]
  74. _install_command = ["gem", "install", "--no-document", "--no-update-sources"]
  75. class NpmInstaller(Installer):
  76. _cleanup_commands = ["rm -Rf ~/.npm /tmp/*"]
  77. _install_command = ["npm", "install", "-g"]
  78. class PipInstaller(Installer):
  79. _install_command = ["pip", "install", "--no-cache-dir", "-r"]
  80. def requirements(self):
  81. """Pip will use its ``--requirements`` feature."""
  82. return [self.file_path] if exists(self.file_path) else []
  83. INSTALLERS = OrderedDict(
  84. [
  85. ("apt", AptInstaller),
  86. ("gem", GemInstaller),
  87. ("npm", NpmInstaller),
  88. ("pip", PipInstaller),
  89. ]
  90. )
  91. def install(installer, file_path):
  92. """Perform a given type of installation from a given file."""
  93. return INSTALLERS[installer](file_path).install()