]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/tools/exec_utils.py
c8de8c7d789b076c7b9dac7e0bb1c2a57439fe07
[nominatim.git] / nominatim / tools / exec_utils.py
1 # SPDX-License-Identifier: GPL-2.0-only
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2022 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Helper functions for executing external programs.
9 """
10 from typing import Any, Optional, Mapping, IO
11 from pathlib import Path
12 import logging
13 import os
14 import subprocess
15 import urllib.request as urlrequest
16 from urllib.parse import urlencode
17
18 from nominatim.typing import StrPath
19 from nominatim.version import NOMINATIM_VERSION
20 from nominatim.db.connection import get_pg_env
21
22 LOG = logging.getLogger()
23
24 def run_api_script(endpoint: str, project_dir: Path,
25                    extra_env: Optional[Mapping[str, str]] = None,
26                    phpcgi_bin: Optional[Path] = None,
27                    params: Optional[Mapping[str, Any]] = None) -> int:
28     """ Execute a Nominatim API function.
29
30         The function needs a project directory that contains the website
31         directory with the scripts to be executed. The scripts will be run
32         using php_cgi. Query parameters can be added as named arguments.
33
34         Returns the exit code of the script.
35     """
36     log = logging.getLogger()
37     webdir = str(project_dir / 'website')
38     query_string = urlencode(params or {})
39
40     env = dict(QUERY_STRING=query_string,
41                SCRIPT_NAME=f'/{endpoint}.php',
42                REQUEST_URI=f'/{endpoint}.php?{query_string}',
43                CONTEXT_DOCUMENT_ROOT=webdir,
44                SCRIPT_FILENAME=f'{webdir}/{endpoint}.php',
45                HTTP_HOST='localhost',
46                HTTP_USER_AGENT='nominatim-tool',
47                REMOTE_ADDR='0.0.0.0',
48                DOCUMENT_ROOT=webdir,
49                REQUEST_METHOD='GET',
50                SERVER_PROTOCOL='HTTP/1.1',
51                GATEWAY_INTERFACE='CGI/1.1',
52                REDIRECT_STATUS='CGI')
53
54     if extra_env:
55         env.update(extra_env)
56
57     if phpcgi_bin is None:
58         cmd = ['/usr/bin/env', 'php-cgi']
59     else:
60         cmd = [str(phpcgi_bin)]
61
62     proc = subprocess.run(cmd, cwd=str(project_dir), env=env,
63                           stdout=subprocess.PIPE,
64                           stderr=subprocess.PIPE,
65                           check=False)
66
67     if proc.returncode != 0 or proc.stderr:
68         if proc.stderr:
69             log.error(proc.stderr.decode('utf-8').replace('\\n', '\n'))
70         else:
71             log.error(proc.stdout.decode('utf-8').replace('\\n', '\n'))
72         return proc.returncode or 1
73
74     result = proc.stdout.decode('utf-8')
75     content_start = result.find('\r\n\r\n')
76
77     print(result[content_start + 4:].replace('\\n', '\n'))
78
79     return 0
80
81
82 def run_php_server(server_address: str, base_dir: StrPath) -> None:
83     """ Run the built-in server from the given directory.
84     """
85     subprocess.run(['/usr/bin/env', 'php', '-S', server_address],
86                    cwd=str(base_dir), check=True)
87
88
89 def run_osm2pgsql(options: Mapping[str, Any]) -> None:
90     """ Run osm2pgsql with the given options.
91     """
92     env = get_pg_env(options['dsn'])
93     cmd = [str(options['osm2pgsql']),
94            '--hstore', '--latlon', '--slim',
95            '--log-progress', 'true',
96            '--number-processes', '1' if options['append'] else str(options['threads']),
97            '--cache', str(options['osm2pgsql_cache']),
98            '--style', str(options['osm2pgsql_style'])
99           ]
100
101     if str(options['osm2pgsql_style']).endswith('.lua'):
102         env['LUA_PATH'] = ';'.join((str(options['osm2pgsql_style_path'] / '?.lua'),
103                                     os.environ.get('LUAPATH', ';')))
104         cmd.extend(('--output', 'flex'))
105     else:
106         cmd.extend(('--output', 'gazetteer'))
107
108     cmd.append('--append' if options['append'] else '--create')
109
110     if options['flatnode_file']:
111         cmd.extend(('--flat-nodes', options['flatnode_file']))
112
113     for key, param in (('slim_data', '--tablespace-slim-data'),
114                        ('slim_index', '--tablespace-slim-index'),
115                        ('main_data', '--tablespace-main-data'),
116                        ('main_index', '--tablespace-main-index')):
117         if options['tablespaces'][key]:
118             cmd.extend((param, options['tablespaces'][key]))
119
120     if options.get('disable_jit', False):
121         env['PGOPTIONS'] = '-c jit=off -c max_parallel_workers_per_gather=0'
122
123     if 'import_data' in options:
124         cmd.extend(('-r', 'xml', '-'))
125     elif isinstance(options['import_file'], list):
126         for fname in options['import_file']:
127             cmd.append(str(fname))
128     else:
129         cmd.append(str(options['import_file']))
130
131     subprocess.run(cmd, cwd=options.get('cwd', '.'),
132                    input=options.get('import_data'),
133                    env=env, check=True)
134
135
136 def get_url(url: str) -> str:
137     """ Get the contents from the given URL and return it as a UTF-8 string.
138     """
139     headers = {"User-Agent": f"Nominatim/{NOMINATIM_VERSION!s}"}
140
141     try:
142         request = urlrequest.Request(url, headers=headers)
143         with urlrequest.urlopen(request) as response: # type: IO[bytes]
144             return response.read().decode('utf-8')
145     except Exception:
146         LOG.fatal('Failed to load URL: %s', url)
147         raise