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

1"""Configuration manager for embedded servers. 

2 

3This object creates configuration files for embedded servers and the server startup script. 

4 

5The configuration is template-based, there are following template subjects defined: 

6 

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 

10 

11Each template receives a :obj:`TemplateArgs` object as arguments. 

12 

13By default, the Manager uses text-only templates from the ``templates`` directory. 

14""" 

15 

16from typing import cast 

17 

18import os 

19import re 

20 

21import gws 

22import gws.base.web 

23import gws.config 

24import gws.config.util 

25import gws.gis.mpx.config 

26import gws.lib.osx 

27 

28from . import core 

29 

30 

31class TemplateArgs(gws.TemplateArgs): 

32 """Arguments for configuration templates.""" 

33 

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.""" 

76 

77 

78_DEFAULT_BASE_TIMEOUT = 60 

79_DEFAULT_SPOOL_TIMEOUT = 300 

80 

81_SERVER_DIR = gws.u.dirname(__file__) 

82 

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] 

89 

90 

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') 

100 

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 

110 

111 self.configure_environment() 

112 self.configure_templates() 

113 

114 def _add_defaults(self, value, type_name): 

115 return gws.u.merge(self.root.specs.read({}, type_name), value) 

116 

117 def configure_environment(self): 

118 """Overwrite config values from the environment.""" 

119 

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) 

130 

131 def configure_templates(self): 

132 gws.config.util.configure_templates_for(self, extra=_DEFAULT_TEMPLATES) 

133 

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) 

136 

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 ) 

160 

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) 

164 

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) 

168 

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) 

172 

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) 

176 

177 args.nginxConfig = self._create_config('server.nginx_config', f'{target_dir}/nginx.conf', args, True) 

178 

179 self._create_config('server.start_script', script_path, args) 

180 

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)) 

184 

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) 

189 

190 lines = [] 

191 indent = 0 

192 

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) 

205 

206 text = '\n'.join(lines) + '\n' 

207 gws.u.write_file(path, text) 

208 return path