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

Read our documentation on viewing source code .

Loading