]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | # -*- coding: utf-8 -*- |
2 | from __future__ import absolute_import | |
3 | ||
4 | import errno | |
5 | import inspect | |
f67539c2 TL |
6 | from ast import literal_eval |
7 | from typing import Any | |
11fdf7f2 | 8 | |
cd265ab1 TL |
9 | from mgr_module import CLICheckNonemptyFileInput |
10 | ||
11fdf7f2 TL |
11 | from . import mgr |
12 | ||
13 | ||
f67539c2 TL |
14 | class Setting: |
15 | """ | |
16 | Setting representation that allows to set a default value and a list of allowed data types. | |
17 | :param default_value: The name of the bucket. | |
18 | :param types: a list consisting of the primary/preferred type and, optionally, | |
19 | secondary/legacy types for backward compatibility. | |
20 | """ | |
21 | ||
22 | def __init__(self, default_value: Any, types: list): | |
23 | if not isinstance(types, list): | |
24 | raise ValueError('Setting types must be a list.') | |
25 | default_value_type = type(default_value) | |
26 | if default_value_type not in types: | |
27 | raise ValueError('Default value type not allowed.') | |
28 | self.default_value = default_value | |
29 | self.types = types | |
30 | ||
31 | def types_as_str(self): | |
32 | return ','.join([x.__name__ for x in self.types]) | |
33 | ||
34 | def cast(self, value): | |
35 | for type_index, setting_type in enumerate(self.types): | |
36 | try: | |
37 | if setting_type.__name__ == 'bool' and str(value).lower() == 'false': | |
38 | return False | |
39 | elif setting_type.__name__ == 'dict': | |
40 | return literal_eval(value) | |
41 | return setting_type(value) | |
42 | except (SyntaxError, TypeError, ValueError) as error: | |
43 | if type_index == len(self.types) - 1: | |
44 | raise error | |
45 | ||
46 | ||
11fdf7f2 TL |
47 | class Options(object): |
48 | """ | |
49 | If you need to store some configuration value please add the config option | |
50 | name as a class attribute to this class. | |
51 | ||
52 | Example:: | |
53 | ||
54 | GRAFANA_API_HOST = ('localhost', str) | |
55 | GRAFANA_API_PORT = (3000, int) | |
56 | """ | |
f67539c2 TL |
57 | ENABLE_BROWSABLE_API = Setting(True, [bool]) |
58 | REST_REQUESTS_TIMEOUT = Setting(45, [int]) | |
11fdf7f2 | 59 | |
adb31ebb | 60 | # AUTHENTICATION ATTEMPTS |
f67539c2 | 61 | ACCOUNT_LOCKOUT_ATTEMPTS = Setting(10, [int]) |
adb31ebb | 62 | |
11fdf7f2 | 63 | # API auditing |
f67539c2 TL |
64 | AUDIT_API_ENABLED = Setting(False, [bool]) |
65 | AUDIT_API_LOG_PAYLOAD = Setting(True, [bool]) | |
11fdf7f2 TL |
66 | |
67 | # RGW settings | |
f67539c2 TL |
68 | RGW_API_ACCESS_KEY = Setting('', [dict, str]) |
69 | RGW_API_SECRET_KEY = Setting('', [dict, str]) | |
70 | RGW_API_ADMIN_RESOURCE = Setting('admin', [str]) | |
f67539c2 | 71 | RGW_API_SSL_VERIFY = Setting(True, [bool]) |
11fdf7f2 TL |
72 | |
73 | # Grafana settings | |
f67539c2 TL |
74 | GRAFANA_API_URL = Setting('', [str]) |
75 | GRAFANA_FRONTEND_API_URL = Setting('', [str]) | |
76 | GRAFANA_API_USERNAME = Setting('admin', [str]) | |
77 | GRAFANA_API_PASSWORD = Setting('admin', [str]) | |
78 | GRAFANA_API_SSL_VERIFY = Setting(True, [bool]) | |
79 | GRAFANA_UPDATE_DASHBOARDS = Setting(False, [bool]) | |
11fdf7f2 TL |
80 | |
81 | # NFS Ganesha settings | |
f67539c2 | 82 | GANESHA_CLUSTERS_RADOS_POOL_NAMESPACE = Setting('', [str]) |
11fdf7f2 TL |
83 | |
84 | # Prometheus settings | |
f67539c2 TL |
85 | PROMETHEUS_API_HOST = Setting('', [str]) |
86 | PROMETHEUS_API_SSL_VERIFY = Setting(True, [bool]) | |
87 | ALERTMANAGER_API_HOST = Setting('', [str]) | |
88 | ALERTMANAGER_API_SSL_VERIFY = Setting(True, [bool]) | |
11fdf7f2 TL |
89 | |
90 | # iSCSI management settings | |
f67539c2 | 91 | ISCSI_API_SSL_VERIFICATION = Setting(True, [bool]) |
11fdf7f2 | 92 | |
9f95a23c TL |
93 | # user management settings |
94 | # Time span of user passwords to expire in days. | |
95 | # The default value is '0' which means that user passwords are | |
96 | # never going to expire. | |
f67539c2 | 97 | USER_PWD_EXPIRATION_SPAN = Setting(0, [int]) |
9f95a23c TL |
98 | # warning levels to notify the user that the password is going |
99 | # to expire soon | |
f67539c2 TL |
100 | USER_PWD_EXPIRATION_WARNING_1 = Setting(10, [int]) |
101 | USER_PWD_EXPIRATION_WARNING_2 = Setting(5, [int]) | |
9f95a23c TL |
102 | |
103 | # Password policy | |
f67539c2 | 104 | PWD_POLICY_ENABLED = Setting(True, [bool]) |
9f95a23c | 105 | # Individual checks |
f67539c2 TL |
106 | PWD_POLICY_CHECK_LENGTH_ENABLED = Setting(True, [bool]) |
107 | PWD_POLICY_CHECK_OLDPWD_ENABLED = Setting(True, [bool]) | |
108 | PWD_POLICY_CHECK_USERNAME_ENABLED = Setting(False, [bool]) | |
109 | PWD_POLICY_CHECK_EXCLUSION_LIST_ENABLED = Setting(False, [bool]) | |
110 | PWD_POLICY_CHECK_COMPLEXITY_ENABLED = Setting(False, [bool]) | |
111 | PWD_POLICY_CHECK_SEQUENTIAL_CHARS_ENABLED = Setting(False, [bool]) | |
112 | PWD_POLICY_CHECK_REPETITIVE_CHARS_ENABLED = Setting(False, [bool]) | |
9f95a23c | 113 | # Settings |
f67539c2 TL |
114 | PWD_POLICY_MIN_LENGTH = Setting(8, [int]) |
115 | PWD_POLICY_MIN_COMPLEXITY = Setting(10, [int]) | |
116 | PWD_POLICY_EXCLUSION_LIST = Setting(','.join(['osd', 'host', 'dashboard', 'pool', | |
117 | 'block', 'nfs', 'ceph', 'monitors', | |
118 | 'gateway', 'logs', 'crush', 'maps']), | |
119 | [str]) | |
9f95a23c | 120 | |
11fdf7f2 TL |
121 | @staticmethod |
122 | def has_default_value(name): | |
123 | return getattr(Settings, name, None) is None or \ | |
f67539c2 | 124 | getattr(Settings, name) == getattr(Options, name).default_value |
11fdf7f2 TL |
125 | |
126 | ||
127 | class SettingsMeta(type): | |
128 | def __getattr__(cls, attr): | |
f67539c2 TL |
129 | setting = getattr(Options, attr) |
130 | return setting.cast(mgr.get_module_option(attr, setting.default_value)) | |
11fdf7f2 TL |
131 | |
132 | def __setattr__(cls, attr, value): | |
133 | if not attr.startswith('_') and hasattr(Options, attr): | |
134 | mgr.set_module_option(attr, str(value)) | |
135 | else: | |
136 | setattr(SettingsMeta, attr, value) | |
137 | ||
138 | def __delattr__(cls, attr): | |
139 | if not attr.startswith('_') and hasattr(Options, attr): | |
140 | mgr.set_module_option(attr, None) | |
141 | ||
142 | ||
143 | # pylint: disable=no-init | |
f67539c2 | 144 | class Settings(object, metaclass=SettingsMeta): |
11fdf7f2 TL |
145 | pass |
146 | ||
147 | ||
148 | def _options_command_map(): | |
149 | def filter_attr(member): | |
150 | return not inspect.isroutine(member) | |
151 | ||
152 | cmd_map = {} | |
f67539c2 | 153 | for option, setting in inspect.getmembers(Options, filter_attr): |
11fdf7f2 TL |
154 | if option.startswith('_'): |
155 | continue | |
156 | key_get = 'dashboard get-{}'.format(option.lower().replace('_', '-')) | |
157 | key_set = 'dashboard set-{}'.format(option.lower().replace('_', '-')) | |
158 | key_reset = 'dashboard reset-{}'.format(option.lower().replace('_', '-')) | |
159 | cmd_map[key_get] = {'name': option, 'type': None} | |
f67539c2 | 160 | cmd_map[key_set] = {'name': option, 'type': setting.types_as_str()} |
11fdf7f2 TL |
161 | cmd_map[key_reset] = {'name': option, 'type': None} |
162 | return cmd_map | |
163 | ||
164 | ||
165 | _OPTIONS_COMMAND_MAP = _options_command_map() | |
166 | ||
167 | ||
168 | def options_command_list(): | |
169 | """ | |
170 | This function generates a list of ``get`` and ``set`` commands | |
171 | for each declared configuration option in class ``Options``. | |
172 | """ | |
173 | def py2ceph(pytype): | |
174 | if pytype == str: | |
175 | return 'CephString' | |
176 | elif pytype == int: | |
177 | return 'CephInt' | |
178 | return 'CephString' | |
179 | ||
180 | cmd_list = [] | |
181 | for cmd, opt in _OPTIONS_COMMAND_MAP.items(): | |
182 | if cmd.startswith('dashboard get'): | |
183 | cmd_list.append({ | |
184 | 'cmd': '{}'.format(cmd), | |
185 | 'desc': 'Get the {} option value'.format(opt['name']), | |
186 | 'perm': 'r' | |
187 | }) | |
188 | elif cmd.startswith('dashboard set'): | |
cd265ab1 | 189 | cmd_entry = { |
11fdf7f2 TL |
190 | 'cmd': '{} name=value,type={}' |
191 | .format(cmd, py2ceph(opt['type'])), | |
192 | 'desc': 'Set the {} option value'.format(opt['name']), | |
193 | 'perm': 'w' | |
cd265ab1 TL |
194 | } |
195 | if handles_secret(cmd): | |
196 | cmd_entry['cmd'] = cmd | |
197 | cmd_entry['desc'] = '{} read from -i <file>'.format(cmd_entry['desc']) | |
198 | cmd_list.append(cmd_entry) | |
11fdf7f2 TL |
199 | elif cmd.startswith('dashboard reset'): |
200 | desc = 'Reset the {} option to its default value'.format( | |
201 | opt['name']) | |
202 | cmd_list.append({ | |
203 | 'cmd': '{}'.format(cmd), | |
204 | 'desc': desc, | |
205 | 'perm': 'w' | |
206 | }) | |
207 | ||
208 | return cmd_list | |
209 | ||
210 | ||
211 | def options_schema_list(): | |
212 | def filter_attr(member): | |
213 | return not inspect.isroutine(member) | |
214 | ||
215 | result = [] | |
f67539c2 | 216 | for option, setting in inspect.getmembers(Options, filter_attr): |
11fdf7f2 TL |
217 | if option.startswith('_'): |
218 | continue | |
f67539c2 TL |
219 | result.append({'name': option, 'default': setting.default_value, |
220 | 'type': setting.types_as_str()}) | |
11fdf7f2 TL |
221 | |
222 | return result | |
223 | ||
224 | ||
cd265ab1 | 225 | def handle_option_command(cmd, inbuf): |
11fdf7f2 TL |
226 | if cmd['prefix'] not in _OPTIONS_COMMAND_MAP: |
227 | return -errno.ENOSYS, '', "Command not found '{}'".format(cmd['prefix']) | |
228 | ||
229 | opt = _OPTIONS_COMMAND_MAP[cmd['prefix']] | |
230 | ||
231 | if cmd['prefix'].startswith('dashboard reset'): | |
232 | delattr(Settings, opt['name']) | |
233 | return 0, 'Option {} reset to default value "{}"'.format( | |
234 | opt['name'], getattr(Settings, opt['name'])), '' | |
235 | elif cmd['prefix'].startswith('dashboard get'): | |
236 | return 0, str(getattr(Settings, opt['name'])), '' | |
237 | elif cmd['prefix'].startswith('dashboard set'): | |
cd265ab1 TL |
238 | if handles_secret(cmd['prefix']): |
239 | value, stdout, stderr = get_secret(inbuf=inbuf) | |
240 | if stderr: | |
241 | return value, stdout, stderr | |
242 | else: | |
243 | value = cmd['value'] | |
f67539c2 TL |
244 | setting = getattr(Options, opt['name']) |
245 | setattr(Settings, opt['name'], setting.cast(value)) | |
11fdf7f2 | 246 | return 0, 'Option {} updated'.format(opt['name']), '' |
cd265ab1 TL |
247 | |
248 | ||
249 | def handles_secret(cmd: str) -> bool: | |
250 | return bool([cmd for secret_word in ['password', 'key'] if (secret_word in cmd)]) | |
251 | ||
252 | ||
522d829b | 253 | @CLICheckNonemptyFileInput(desc='password/secret') |
cd265ab1 TL |
254 | def get_secret(inbuf=None): |
255 | return inbuf, None, None |