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