Config.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. #!/usr/bin/env python3
  2. # Contest Management System - http://cms-dev.github.io/
  3. # Copyright © 2011-2016 Luca Wehrstedt <luca.wehrstedt@gmail.com>
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU Affero General Public License as
  7. # published by the Free Software Foundation, either version 3 of the
  8. # License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU Affero General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Affero General Public License
  16. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. import errno
  18. import json
  19. import logging
  20. import os
  21. import sys
  22. import pkg_resources
  23. from cmsranking.Logger import add_file_handler
  24. logger = logging.getLogger(__name__)
  25. CMS_RANKING_CONFIG_ENV_VAR = "CMS_RANKING_CONFIG"
  26. class Config:
  27. """An object holding the current configuration.
  28. """
  29. def __init__(self):
  30. """Fill this object with the default values for each key.
  31. """
  32. # Connection.
  33. self.bind_address = ''
  34. self.http_port = 8890
  35. self.https_port = None
  36. self.https_certfile = None
  37. self.https_keyfile = None
  38. self.timeout = 600 # 10 minutes (in seconds)
  39. # Authentication.
  40. self.realm_name = 'Scoreboard'
  41. self.username = 'usern4me'
  42. self.password = 'passw0rd'
  43. # Buffers
  44. self.buffer_size = 100 # Needs to be strictly positive.
  45. # File system.
  46. # TODO: move to cmscommon as it is used both here and in cms/conf.py
  47. bin_path = os.path.join(os.getcwd(), sys.argv[0])
  48. bin_name = os.path.basename(bin_path)
  49. bin_is_python = bin_name in ["ipython", "python", "python2", "python3"]
  50. bin_in_installed_path = bin_path.startswith(sys.prefix) or (
  51. hasattr(sys, 'real_prefix')
  52. and bin_path.startswith(sys.real_prefix))
  53. self.installed = bin_in_installed_path and not bin_is_python
  54. self.web_dir = pkg_resources.resource_filename("cmsranking", "static")
  55. if self.installed:
  56. self.log_dir = os.path.join("/", "var", "local", "log",
  57. "cms", "ranking")
  58. self.lib_dir = os.path.join("/", "var", "local", "lib",
  59. "cms", "ranking")
  60. self.conf_paths = [os.path.join("/", "usr", "local", "etc",
  61. "cms.ranking.conf"),
  62. os.path.join("/", "etc", "cms.ranking.conf")]
  63. else:
  64. self.log_dir = os.path.join("log", "ranking")
  65. self.lib_dir = os.path.join("lib", "ranking")
  66. self.conf_paths = [os.path.join(".", "config", "cms.ranking.conf"),
  67. os.path.join("/", "usr", "local", "etc",
  68. "cms.ranking.conf"),
  69. os.path.join("/", "etc", "cms.ranking.conf")]
  70. # Allow users to override config file path using environment
  71. # variable 'CMS_RANKING_CONFIG'.
  72. if CMS_RANKING_CONFIG_ENV_VAR in os.environ:
  73. self.conf_paths = [os.environ[CMS_RANKING_CONFIG_ENV_VAR]] \
  74. + self.conf_paths
  75. def get(self, key):
  76. """Get the config value for the given key.
  77. """
  78. return getattr(self, key)
  79. def load(self, config_override_fobj=None):
  80. """Look for config files on disk and load them.
  81. """
  82. # If a command-line override is given it is used exclusively.
  83. if config_override_fobj is not None:
  84. if not self._load_one(config_override_fobj):
  85. sys.exit(1)
  86. else:
  87. if not self._load_many(self.conf_paths):
  88. sys.exit(1)
  89. try:
  90. os.makedirs(self.lib_dir)
  91. except OSError:
  92. pass # We assume the directory already exists...
  93. try:
  94. os.makedirs(self.web_dir)
  95. except OSError:
  96. pass # We assume the directory already exists...
  97. try:
  98. os.makedirs(self.log_dir)
  99. except OSError:
  100. pass # We assume the directory already exists...
  101. add_file_handler(self.log_dir)
  102. def _load_many(self, conf_paths):
  103. """Load the first existing config file among the given ones.
  104. Take a list of paths where config files may reside and attempt
  105. to load the first one that exists.
  106. conf_paths([str]): paths of config file candidates, from most
  107. to least prioritary.
  108. returns (bool): whether loading was successful.
  109. """
  110. for conf_path in conf_paths:
  111. try:
  112. with open(conf_path, "rt", encoding="utf-8") as conf_fobj:
  113. logger.info("Using config file %s.", conf_path)
  114. return self._load_one(conf_fobj)
  115. except FileNotFoundError:
  116. # If it doesn't exist we just skip to the next one.
  117. pass
  118. except OSError as error:
  119. logger.critical("Unable to access config file %s: [%s] %s.",
  120. conf_path, errno.errorcode[error.errno],
  121. os.strerror(error.errno))
  122. return False
  123. logger.warning("No config file found, using hardcoded defaults.")
  124. return True
  125. def _load_one(self, conf_fobj):
  126. """Populate config parameters from the given file.
  127. Parse it as JSON and store in self all configuration properties
  128. it defines. Log critical message and return False if anything
  129. goes wrong or seems odd.
  130. conf_fobj (file-like object): the config file.
  131. returns (bool): whether parsing was successful.
  132. """
  133. # Parse config file.
  134. try:
  135. data = json.load(conf_fobj)
  136. except ValueError:
  137. logger.critical("Config file is invalid JSON.")
  138. return False
  139. # Store every config property.
  140. for key, value in data.items():
  141. if key.startswith("_"):
  142. continue
  143. if not hasattr(self, key):
  144. logger.critical("Invalid field %s in config file, maybe a "
  145. "typo? (use leading underscore to ignore).",
  146. key)
  147. return False
  148. setattr(self, key, value)
  149. return True