config.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. # Use of this source code is governed by a BSD-style
  2. # license that can be found in the LICENSE file.
  3. # Copyright 2019 The OSArchiver Authors. All rights reserved.
  4. """
  5. Configuration class to handle osarchiver config
  6. """
  7. import re
  8. import logging
  9. import configparser
  10. from osarchiver.archiver import Archiver
  11. from osarchiver.destination import factory as dst_factory
  12. from osarchiver.source import factory as src_factory
  13. BOOLEAN_OPTIONS = ['delete_data', 'archive_data', 'enable', 'foreign_key_check']
  14. class Config():
  15. """
  16. This class is able to read an ini configuration file and instanciate
  17. Archivers to be run
  18. """
  19. def __init__(self, file_path=None, dry_run=False):
  20. self.file_path = file_path
  21. """
  22. Config class instantiator. Instantiate a configparser
  23. """
  24. self.parser = configparser.ConfigParser(
  25. interpolation=configparser.ExtendedInterpolation())
  26. self.loaded = 0
  27. self._archivers = []
  28. self._sources = []
  29. self._destinations = []
  30. self.dry_run = dry_run
  31. def load(self, file_path=None):
  32. """
  33. Load a file given in arguments and make the configparser read it
  34. and return the parser
  35. """
  36. if self.loaded == 1:
  37. return self.parser
  38. if file_path is not None:
  39. self.file_path = file_path
  40. logging.info("Loading configuration file %s", self.file_path)
  41. loaded_files = self.parser.read(self.file_path)
  42. self.loaded = len(loaded_files)
  43. logging.debug("Config object loaded")
  44. logging.debug(loaded_files)
  45. return self.parser
  46. def sections(self):
  47. """
  48. return call to sections() of ConfigParser for the config file
  49. """
  50. if self.loaded == 0:
  51. self.load()
  52. return self.parser.sections()
  53. def section(self, name, default=True):
  54. """
  55. return a dict of key/value for the given section
  56. if defaults is set to False, it will remove defaults value from the section
  57. """
  58. if not name or not self.parser.has_section(name):
  59. return {}
  60. default_keys = []
  61. if not default:
  62. default_keys = [k for k, v in self.parser.items('DEFAULT')]
  63. return {
  64. k: v for k, v in self.parser.items(name) if k not in default_keys
  65. }
  66. @property
  67. def archivers(self):
  68. """
  69. This method load the configuration and instantiate all the Source and
  70. Destination objects needed for each archiver
  71. """
  72. self.load()
  73. if self._archivers:
  74. return self._archivers
  75. archiver_sections = [
  76. a for a in self.sections() if str(a).startswith('archiver:')
  77. ]
  78. def args_factory(section):
  79. """
  80. Generic function that takes a section from configuration file
  81. and return arguments that are passed to source or destination
  82. factory
  83. """
  84. args_factory = {
  85. k: v if k not in BOOLEAN_OPTIONS else self.parser.getboolean(
  86. section, k)
  87. for (k, v) in self.parser.items(section)
  88. }
  89. args_factory['name'] = re.sub('^(src|dst):', '', section)
  90. args_factory['dry_run'] = self.dry_run
  91. args_factory['conf'] = self
  92. logging.debug(
  93. "'%s' factory parameters: %s", args_factory['name'], {
  94. k: v if k != 'password' else '***********'
  95. for (k, v) in args_factory.items()
  96. })
  97. return args_factory
  98. # Instanciate archivers:
  99. # One archiver is bascally a process of archiving
  100. # One archiver got one source and at least one destination
  101. # It means we have a total of source*count(destination)
  102. # processes to run per archiver
  103. for archiver in archiver_sections:
  104. # If enable: 0 in archiver config ignore it
  105. if not self.parser.getboolean(archiver, 'enable'):
  106. logging.info("Archiver %s is disabled, ignoring it", archiver)
  107. continue
  108. # src and dst sections are comma, semicolon, or carriage return
  109. # separated name
  110. src_sections = [
  111. 'src:{}'.format(i.strip())
  112. for i in re.split(r'\n|,|;', self.parser[archiver]['src'])
  113. ]
  114. # destination is not mandatory
  115. # usefull to just delete data from DB
  116. dst_sections = [
  117. 'dst:{}'.format(i.strip()) for i in re.split(
  118. r'\n|,|;', self.parser[archiver].get('dst', '')) if i
  119. ]
  120. for src_section in src_sections:
  121. src_args_factory = args_factory(src_section)
  122. src = src_factory(**src_args_factory)
  123. destinations = []
  124. for dst_section in dst_sections:
  125. dst_args_factory = args_factory(dst_section)
  126. dst_args_factory['source'] = src
  127. dst = dst_factory(**dst_args_factory)
  128. destinations.append(dst)
  129. self._archivers.append(
  130. Archiver(name=re.sub('^archiver:', '', archiver),
  131. src=src,
  132. dst=destinations,
  133. conf=self))
  134. return self._archivers
  135. @property
  136. def sources(self):
  137. """
  138. Return a list of Sources object after having loaded the
  139. configuration file
  140. """
  141. self.load()
  142. self._sources.extend(
  143. [s for s in self.sections() if str(s).startswith('src:')])
  144. return self._sources
  145. @property
  146. def destinations(self):
  147. """
  148. Return a list of Destinations object after having loaded the
  149. configuration file
  150. """
  151. self.load()
  152. self._destinations.extend(
  153. [d for d in self.sections() if str(d).startswith('dst:')])
  154. return self._destinations