Coverage for gws-app/gws/server/manager.py: 0%

105 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-17 01:37 +0200

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 grp 

19import os 

20import pwd 

21import re 

22 

23import gws 

24import gws.base.web 

25import gws.config 

26import gws.config.util 

27import gws.gis.mpx.config 

28import gws.lib.osx 

29 

30from . import core 

31 

32 

33class TemplateArgs(gws.TemplateArgs): 

34 """Arguments for configuration templates.""" 

35 

36 root: gws.Root 

37 """Root object.""" 

38 inContainer: bool 

39 """True if we're running in a container.""" 

40 userName: str 

41 """User name.""" 

42 groupName: str 

43 """Group name.""" 

44 gwsEnv: dict 

45 """A dict of GWS environment variables.""" 

46 mapproxyPid: str 

47 """Mapproxy pid path.""" 

48 mapproxySocket: str 

49 """Mapproxy socket path.""" 

50 nginxPid: str 

51 """nginx pid path.""" 

52 serverDir: str 

53 """Absolute path to app/server directory.""" 

54 spoolPid: str 

55 """Spooler pid path.""" 

56 spoolSocket: str 

57 """Spooler socket path.""" 

58 uwsgi: str 

59 """uWSGI backend name.""" 

60 webPid: str 

61 """Web server pid path.""" 

62 webSocket: str 

63 """Web server socket path.""" 

64 

65 

66_DEFAULT_BASE_TIMEOUT = 60 

67_DEFAULT_SPOOL_TIMEOUT = 300 

68 

69_SERVER_DIR = gws.u.dirname(__file__) 

70 

71_DEFAULT_TEMPLATES = [ 

72 gws.Config(type='text', subject='server.rsyslog_config', path=f'{_SERVER_DIR}/templates/rsyslog_config.cx.txt'), 

73 gws.Config(type='text', subject='server.nginx_config', path=f'{_SERVER_DIR}/templates/nginx_config.cx.txt'), 

74 gws.Config(type='text', subject='server.uwsgi_config', path=f'{_SERVER_DIR}/templates/uwsgi_config.cx.txt'), 

75] 

76 

77 

78class Object(gws.ServerManager): 

79 def configure(self): 

80 self.configure_environment() 

81 self.configure_templates() 

82 

83 def configure_environment(self): 

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

85 

86 p = gws.env.GWS_LOG_LEVEL 

87 if p: 

88 cast(core.Config, self.config).log.level = p 

89 p = gws.env.GWS_WEB_WORKERS 

90 if p: 

91 cast(core.Config, self.config).web.workers = int(p) 

92 p = gws.env.GWS_SPOOL_WORKERS 

93 if p: 

94 cast(core.Config, self.config).spool.workers = int(p) 

95 

96 def configure_templates(self): 

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

98 

99 def create_server_configs(self, target_dir, script_path, pid_paths): 

100 args = TemplateArgs( 

101 root=self.root, 

102 inContainer=gws.c.env.GWS_IN_CONTAINER, 

103 userName=pwd.getpwuid(gws.c.UID).pw_name, 

104 groupName=grp.getgrgid(gws.c.GID).gr_name, 

105 gwsEnv={k: v for k, v in sorted(os.environ.items()) if k.startswith('GWS_')}, 

106 mapproxyPid=pid_paths['mapproxy'], 

107 mapproxySocket=f'{gws.c.TMP_DIR}/mapproxy.uwsgi.sock', 

108 nginxPid=pid_paths['nginx'], 

109 serverDir=_SERVER_DIR, 

110 spoolPid=pid_paths['spool'], 

111 spoolSocket=f'{gws.c.TMP_DIR}/spool.uwsgi.sock', 

112 webPid=pid_paths['web'], 

113 webSocket=f'{gws.c.TMP_DIR}/web.uwsgi.sock', 

114 ) 

115 

116 commands = [] 

117 

118 if args.inContainer: 

119 path = self._create_config('server.rsyslog_config', f'{target_dir}/syslog.conf', args) 

120 commands.append(f'rsyslogd -i {gws.c.PIDS_DIR}/rsyslogd.pid -f {path}') 

121 

122 if self.cfg('web.enabled'): 

123 args.uwsgi = 'web' 

124 path = self._create_config('server.uwsgi_config', f'{target_dir}/uwsgi_web.ini', args) 

125 commands.append(f'uwsgi --ini {path}') 

126 

127 if self.cfg('mapproxy.enabled') and gws.u.is_file(gws.gis.mpx.config.CONFIG_PATH): 

128 args.uwsgi = 'mapproxy' 

129 path = self._create_config('server.uwsgi_config', f'{target_dir}/uwsgi_mapproxy.ini', args) 

130 commands.append(f'uwsgi --ini {path}') 

131 

132 if self.cfg('spool.enabled'): 

133 args.uwsgi = 'spool' 

134 path = self._create_config('server.uwsgi_config', f'{target_dir}/uwsgi_spool.ini', args) 

135 commands.append(f'uwsgi --ini {path}') 

136 

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

138 commands.append(f'exec nginx -c {path}') 

139 

140 gws.u.write_file(script_path, '\n'.join(commands) + '\n') 

141 

142 def _create_config(self, subject: str, path: str, args: TemplateArgs, is_nginx=False) -> str: 

143 tpl = self.root.app.templateMgr.find_template(subject, where=[self]) 

144 res = tpl.render(gws.TemplateRenderInput(args=args)) 

145 

146 text = str(res.content) 

147 if is_nginx: 

148 text = re.sub(r'\s*{\s*', ' {\n', text) 

149 text = re.sub(r'\s*}\s*', '\n}\n', text) 

150 

151 lines = [] 

152 indent = 0 

153 

154 for line in text.split('\n'): 

155 line = re.sub(r'\s+', ' ', line.strip()) 

156 if not line: 

157 continue 

158 if is_nginx: 

159 if line == '}': 

160 indent -= 1 

161 lines.append((' ' * (indent * 4)) + line) 

162 if '{' in line: 

163 indent += 1 

164 else: 

165 lines.append(line) 

166 

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

168 gws.u.write_file(path, text) 

169 return path