Coverage for gws-app / gws / server / manager.py: 66%
131 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-03 10:12 +0100
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-03 10:12 +0100
1"""Configuration manager for embedded servers.
3This object creates configuration files for embedded servers and the server startup script.
5The configuration is template-based, there are following template subjects defined:
7- ``server.rsyslog_config`` - for the embedded ``rsyslogd`` daemon
8- ``server.uwsgi_config`` - for backend uWSGI servers (the ``uwsgi`` argument contains the specific backend name)
9- ``server.nginx_config`` - for the frontend NGINX proxy
11Each template receives a :obj:`TemplateArgs` object as arguments.
13By default, the Manager uses text-only templates from the ``templates`` directory.
14"""
16from typing import cast
18import os
19import re
21import gws
22import gws.base.web
23import gws.config
24import gws.config.util
25import gws.gis.mpx.config
26import gws.lib.osx
28from . import core
31class TemplateArgs(gws.TemplateArgs):
32 """Arguments for configuration templates."""
34 root: gws.Root
35 """Root object."""
36 serverDir: str
37 """Absolute path to app/server directory."""
38 gwsEnv: dict
39 """A dict of GWS environment variables."""
40 inContainer: bool
41 """True if we're running in a container."""
42 uwsgi: str
43 """uWSGI backend name."""
44 userName: str
45 """User name."""
46 groupName: str
47 """User group name."""
48 homeDir: str
49 """User home directory."""
50 mapproxyConfig: str
51 """Mapproxy config path."""
52 mapproxyPid: str
53 """Mapproxy pid path."""
54 mapproxySocket: str
55 """Mapproxy socket path."""
56 nginxConfig: str
57 """nginx config path."""
58 nginxPid: str
59 """nginx pid path."""
60 spoolConfig: str
61 """Spooler config path."""
62 spoolPid: str
63 """Spooler pid path."""
64 spoolSocket: str
65 """Spooler socket path."""
66 syslogConfig: str
67 """Syslog config path."""
68 syslogPid: str
69 """Syslog pid path."""
70 webConfig: str
71 """Web server config path."""
72 webPid: str
73 """Web server pid path."""
74 webSocket: str
75 """Web server socket path."""
78_DEFAULT_BASE_TIMEOUT = 60
79_DEFAULT_SPOOL_TIMEOUT = 300
81_SERVER_DIR = gws.u.dirname(__file__)
83_DEFAULT_TEMPLATES = [
84 gws.Config(type='text', subject='server.rsyslog_config', path=f'{_SERVER_DIR}/templates/rsyslog_config.cx.txt'),
85 gws.Config(type='text', subject='server.nginx_config', path=f'{_SERVER_DIR}/templates/nginx_config.cx.txt'),
86 gws.Config(type='text', subject='server.uwsgi_config', path=f'{_SERVER_DIR}/templates/uwsgi_config.cx.txt'),
87 gws.Config(type='text', subject='server.start_script', path=f'{_SERVER_DIR}/templates/start_script.cx.txt'),
88]
91class Object(gws.ServerManager):
92 def configure(self):
93 self.config = self._add_defaults(self.config, 'gws.server.core.Config')
94 self.config.log = self._add_defaults(self.config.log, 'gws.server.core.LogConfig')
95 self.config.mapproxy = self._add_defaults(self.config.mapproxy, 'gws.server.core.MapproxyConfig')
96 self.config.monitor = self._add_defaults(self.config.monitor, 'gws.server.core.MonitorConfig')
97 self.config.qgis = self._add_defaults(self.config.qgis, 'gws.server.core.QgisConfig')
98 self.config.spool = self._add_defaults(self.config.spool, 'gws.server.core.SpoolConfig')
99 self.config.web = self._add_defaults(self.config.web, 'gws.server.core.WebConfig')
101 # deprecated 'enabled' keys
102 if self.config.mapproxy.enabled is False:
103 self.config.withMapproxy = False
104 if self.config.spool.enabled is False:
105 self.config.withSpool = False
106 if self.config.web.enabled is False:
107 self.config.withWeb = False
108 if self.config.monitor.enabled is False:
109 self.config.withMonitor = False
111 self.configure_environment()
112 self.configure_templates()
114 def _add_defaults(self, value, type_name):
115 return gws.u.merge(self.root.specs.read({}, type_name), value)
117 def configure_environment(self):
118 """Overwrite config values from the environment."""
120 cfg = cast(core.Config, self.config)
121 p = gws.env.GWS_LOG_LEVEL
122 if p:
123 cfg.log.level = p
124 p = gws.env.GWS_WEB_WORKERS
125 if p:
126 cfg.web.workers = int(p)
127 p = gws.env.GWS_SPOOL_WORKERS
128 if p:
129 cfg.spool.workers = int(p)
131 def configure_templates(self):
132 gws.config.util.configure_templates_for(self, extra=_DEFAULT_TEMPLATES)
134 def create_server_configs(self, target_dir, script_path, pid_paths):
135 ui = gws.lib.osx.user_info(gws.c.UID, gws.c.GID)
137 args = TemplateArgs(
138 root=self.root,
139 serverDir=_SERVER_DIR,
140 gwsEnv={k: v for k, v in sorted(os.environ.items()) if k.startswith('GWS_')},
141 inContainer=gws.c.env.GWS_IN_CONTAINER,
142 uwsgi='',
143 userName=ui['pw_name'],
144 groupName=ui['gr_name'],
145 homeDir=ui['pw_dir'],
146 mapproxyConfig='',
147 mapproxyPid=pid_paths['mapproxy'],
148 mapproxySocket=f'{gws.c.TMP_DIR}/mapproxy.uwsgi.sock',
149 nginxConfig='',
150 nginxPid=pid_paths['nginx'],
151 spoolConfig='',
152 spoolPid=pid_paths['spool'],
153 spoolSocket=f'{gws.c.TMP_DIR}/spool.uwsgi.sock',
154 syslogConfig='',
155 syslogPid='',
156 webConfig='',
157 webPid=pid_paths['web'],
158 webSocket=f'{gws.c.TMP_DIR}/web.uwsgi.sock',
159 )
161 if args.inContainer:
162 args.syslogPid = f'{gws.c.PIDS_DIR}/rsyslogd.pid'
163 args.syslogConfig = self._create_config('server.rsyslog_config', f'{target_dir}/syslog.conf', args)
165 if self.cfg('withWeb'):
166 args.uwsgi = 'web'
167 args.webConfig = self._create_config('server.uwsgi_config', f'{target_dir}/uwsgi_web.ini', args)
169 if self.cfg('withMapproxy') and gws.u.is_file(gws.gis.mpx.config.CONFIG_PATH):
170 args.uwsgi = 'mapproxy'
171 args.mapproxyConfig = self._create_config('server.uwsgi_config', f'{target_dir}/uwsgi_mapproxy.ini', args)
173 if self.cfg('withSpool'):
174 args.uwsgi = 'spool'
175 args.spoolConfig = self._create_config('server.uwsgi_config', f'{target_dir}/uwsgi_spool.ini', args)
177 args.nginxConfig = self._create_config('server.nginx_config', f'{target_dir}/nginx.conf', args, True)
179 self._create_config('server.start_script', script_path, args)
181 def _create_config(self, subject: str, path: str, args: TemplateArgs, is_nginx=False) -> str:
182 tpl = gws.u.require(self.root.app.templateMgr.find_template(subject, where=[self]))
183 res = tpl.render(gws.TemplateRenderInput(args=args))
185 text = str(res.content)
186 if is_nginx:
187 text = re.sub(r'\s*{\s*', ' {\n', text)
188 text = re.sub(r'\s*}\s*', '\n}\n', text)
190 lines = []
191 indent = 0
193 for line in text.split('\n'):
194 line = re.sub(r'\s+', ' ', line.strip())
195 if not line:
196 continue
197 if is_nginx:
198 if line == '}':
199 indent -= 1
200 lines.append((' ' * (indent * 4)) + line)
201 if '{' in line:
202 indent += 1
203 else:
204 lines.append(line)
206 text = '\n'.join(lines) + '\n'
207 gws.u.write_file(path, text)
208 return path