xref: /aosp_15_r20/external/curl/tests/http/testenv/caddy.py (revision 6236dae45794135f37c4eb022389c904c8b0090d)
1*6236dae4SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*6236dae4SAndroid Build Coastguard Worker# -*- coding: utf-8 -*-
3*6236dae4SAndroid Build Coastguard Worker#***************************************************************************
4*6236dae4SAndroid Build Coastguard Worker#                                  _   _ ____  _
5*6236dae4SAndroid Build Coastguard Worker#  Project                     ___| | | |  _ \| |
6*6236dae4SAndroid Build Coastguard Worker#                             / __| | | | |_) | |
7*6236dae4SAndroid Build Coastguard Worker#                            | (__| |_| |  _ <| |___
8*6236dae4SAndroid Build Coastguard Worker#                             \___|\___/|_| \_\_____|
9*6236dae4SAndroid Build Coastguard Worker#
10*6236dae4SAndroid Build Coastguard Worker# Copyright (C) Daniel Stenberg, <[email protected]>, et al.
11*6236dae4SAndroid Build Coastguard Worker#
12*6236dae4SAndroid Build Coastguard Worker# This software is licensed as described in the file COPYING, which
13*6236dae4SAndroid Build Coastguard Worker# you should have received as part of this distribution. The terms
14*6236dae4SAndroid Build Coastguard Worker# are also available at https://curl.se/docs/copyright.html.
15*6236dae4SAndroid Build Coastguard Worker#
16*6236dae4SAndroid Build Coastguard Worker# You may opt to use, copy, modify, merge, publish, distribute and/or sell
17*6236dae4SAndroid Build Coastguard Worker# copies of the Software, and permit persons to whom the Software is
18*6236dae4SAndroid Build Coastguard Worker# furnished to do so, under the terms of the COPYING file.
19*6236dae4SAndroid Build Coastguard Worker#
20*6236dae4SAndroid Build Coastguard Worker# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21*6236dae4SAndroid Build Coastguard Worker# KIND, either express or implied.
22*6236dae4SAndroid Build Coastguard Worker#
23*6236dae4SAndroid Build Coastguard Worker# SPDX-License-Identifier: curl
24*6236dae4SAndroid Build Coastguard Worker#
25*6236dae4SAndroid Build Coastguard Worker###########################################################################
26*6236dae4SAndroid Build Coastguard Worker#
27*6236dae4SAndroid Build Coastguard Workerimport logging
28*6236dae4SAndroid Build Coastguard Workerimport os
29*6236dae4SAndroid Build Coastguard Workerimport subprocess
30*6236dae4SAndroid Build Coastguard Workerimport time
31*6236dae4SAndroid Build Coastguard Workerfrom datetime import timedelta, datetime
32*6236dae4SAndroid Build Coastguard Workerfrom json import JSONEncoder
33*6236dae4SAndroid Build Coastguard Worker
34*6236dae4SAndroid Build Coastguard Workerfrom .curl import CurlClient
35*6236dae4SAndroid Build Coastguard Workerfrom .env import Env
36*6236dae4SAndroid Build Coastguard Worker
37*6236dae4SAndroid Build Coastguard Worker
38*6236dae4SAndroid Build Coastguard Workerlog = logging.getLogger(__name__)
39*6236dae4SAndroid Build Coastguard Worker
40*6236dae4SAndroid Build Coastguard Worker
41*6236dae4SAndroid Build Coastguard Workerclass Caddy:
42*6236dae4SAndroid Build Coastguard Worker
43*6236dae4SAndroid Build Coastguard Worker    def __init__(self, env: Env):
44*6236dae4SAndroid Build Coastguard Worker        self.env = env
45*6236dae4SAndroid Build Coastguard Worker        self._caddy = os.environ['CADDY'] if 'CADDY' in os.environ else env.caddy
46*6236dae4SAndroid Build Coastguard Worker        self._caddy_dir = os.path.join(env.gen_dir, 'caddy')
47*6236dae4SAndroid Build Coastguard Worker        self._docs_dir = os.path.join(self._caddy_dir, 'docs')
48*6236dae4SAndroid Build Coastguard Worker        self._conf_file = os.path.join(self._caddy_dir, 'Caddyfile')
49*6236dae4SAndroid Build Coastguard Worker        self._error_log = os.path.join(self._caddy_dir, 'caddy.log')
50*6236dae4SAndroid Build Coastguard Worker        self._tmp_dir = os.path.join(self._caddy_dir, 'tmp')
51*6236dae4SAndroid Build Coastguard Worker        self._process = None
52*6236dae4SAndroid Build Coastguard Worker        self._rmf(self._error_log)
53*6236dae4SAndroid Build Coastguard Worker
54*6236dae4SAndroid Build Coastguard Worker    @property
55*6236dae4SAndroid Build Coastguard Worker    def docs_dir(self):
56*6236dae4SAndroid Build Coastguard Worker        return self._docs_dir
57*6236dae4SAndroid Build Coastguard Worker
58*6236dae4SAndroid Build Coastguard Worker    @property
59*6236dae4SAndroid Build Coastguard Worker    def port(self) -> int:
60*6236dae4SAndroid Build Coastguard Worker        return self.env.caddy_https_port
61*6236dae4SAndroid Build Coastguard Worker
62*6236dae4SAndroid Build Coastguard Worker    def clear_logs(self):
63*6236dae4SAndroid Build Coastguard Worker        self._rmf(self._error_log)
64*6236dae4SAndroid Build Coastguard Worker
65*6236dae4SAndroid Build Coastguard Worker    def is_running(self):
66*6236dae4SAndroid Build Coastguard Worker        if self._process:
67*6236dae4SAndroid Build Coastguard Worker            self._process.poll()
68*6236dae4SAndroid Build Coastguard Worker            return self._process.returncode is None
69*6236dae4SAndroid Build Coastguard Worker        return False
70*6236dae4SAndroid Build Coastguard Worker
71*6236dae4SAndroid Build Coastguard Worker    def start_if_needed(self):
72*6236dae4SAndroid Build Coastguard Worker        if not self.is_running():
73*6236dae4SAndroid Build Coastguard Worker            return self.start()
74*6236dae4SAndroid Build Coastguard Worker        return True
75*6236dae4SAndroid Build Coastguard Worker
76*6236dae4SAndroid Build Coastguard Worker    def start(self, wait_live=True):
77*6236dae4SAndroid Build Coastguard Worker        self._mkpath(self._tmp_dir)
78*6236dae4SAndroid Build Coastguard Worker        if self._process:
79*6236dae4SAndroid Build Coastguard Worker            self.stop()
80*6236dae4SAndroid Build Coastguard Worker        self._write_config()
81*6236dae4SAndroid Build Coastguard Worker        args = [
82*6236dae4SAndroid Build Coastguard Worker            self._caddy, 'run'
83*6236dae4SAndroid Build Coastguard Worker        ]
84*6236dae4SAndroid Build Coastguard Worker        caddyerr = open(self._error_log, 'a')
85*6236dae4SAndroid Build Coastguard Worker        self._process = subprocess.Popen(args=args, cwd=self._caddy_dir, stderr=caddyerr)
86*6236dae4SAndroid Build Coastguard Worker        if self._process.returncode is not None:
87*6236dae4SAndroid Build Coastguard Worker            return False
88*6236dae4SAndroid Build Coastguard Worker        return not wait_live or self.wait_live(timeout=timedelta(seconds=5))
89*6236dae4SAndroid Build Coastguard Worker
90*6236dae4SAndroid Build Coastguard Worker    def stop_if_running(self):
91*6236dae4SAndroid Build Coastguard Worker        if self.is_running():
92*6236dae4SAndroid Build Coastguard Worker            return self.stop()
93*6236dae4SAndroid Build Coastguard Worker        return True
94*6236dae4SAndroid Build Coastguard Worker
95*6236dae4SAndroid Build Coastguard Worker    def stop(self, wait_dead=True):
96*6236dae4SAndroid Build Coastguard Worker        self._mkpath(self._tmp_dir)
97*6236dae4SAndroid Build Coastguard Worker        if self._process:
98*6236dae4SAndroid Build Coastguard Worker            self._process.terminate()
99*6236dae4SAndroid Build Coastguard Worker            self._process.wait(timeout=2)
100*6236dae4SAndroid Build Coastguard Worker            self._process = None
101*6236dae4SAndroid Build Coastguard Worker            return not wait_dead or self.wait_dead(timeout=timedelta(seconds=5))
102*6236dae4SAndroid Build Coastguard Worker        return True
103*6236dae4SAndroid Build Coastguard Worker
104*6236dae4SAndroid Build Coastguard Worker    def restart(self):
105*6236dae4SAndroid Build Coastguard Worker        self.stop()
106*6236dae4SAndroid Build Coastguard Worker        return self.start()
107*6236dae4SAndroid Build Coastguard Worker
108*6236dae4SAndroid Build Coastguard Worker    def wait_dead(self, timeout: timedelta):
109*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=self.env, run_dir=self._tmp_dir)
110*6236dae4SAndroid Build Coastguard Worker        try_until = datetime.now() + timeout
111*6236dae4SAndroid Build Coastguard Worker        while datetime.now() < try_until:
112*6236dae4SAndroid Build Coastguard Worker            check_url = f'https://{self.env.domain1}:{self.port}/'
113*6236dae4SAndroid Build Coastguard Worker            r = curl.http_get(url=check_url)
114*6236dae4SAndroid Build Coastguard Worker            if r.exit_code != 0:
115*6236dae4SAndroid Build Coastguard Worker                return True
116*6236dae4SAndroid Build Coastguard Worker            log.debug(f'waiting for caddy to stop responding: {r}')
117*6236dae4SAndroid Build Coastguard Worker            time.sleep(.1)
118*6236dae4SAndroid Build Coastguard Worker        log.debug(f"Server still responding after {timeout}")
119*6236dae4SAndroid Build Coastguard Worker        return False
120*6236dae4SAndroid Build Coastguard Worker
121*6236dae4SAndroid Build Coastguard Worker    def wait_live(self, timeout: timedelta):
122*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=self.env, run_dir=self._tmp_dir)
123*6236dae4SAndroid Build Coastguard Worker        try_until = datetime.now() + timeout
124*6236dae4SAndroid Build Coastguard Worker        while datetime.now() < try_until:
125*6236dae4SAndroid Build Coastguard Worker            check_url = f'https://{self.env.domain1}:{self.port}/'
126*6236dae4SAndroid Build Coastguard Worker            r = curl.http_get(url=check_url)
127*6236dae4SAndroid Build Coastguard Worker            if r.exit_code == 0:
128*6236dae4SAndroid Build Coastguard Worker                return True
129*6236dae4SAndroid Build Coastguard Worker            time.sleep(.1)
130*6236dae4SAndroid Build Coastguard Worker        log.error(f"Caddy still not responding after {timeout}")
131*6236dae4SAndroid Build Coastguard Worker        return False
132*6236dae4SAndroid Build Coastguard Worker
133*6236dae4SAndroid Build Coastguard Worker    def _rmf(self, path):
134*6236dae4SAndroid Build Coastguard Worker        if os.path.exists(path):
135*6236dae4SAndroid Build Coastguard Worker            return os.remove(path)
136*6236dae4SAndroid Build Coastguard Worker
137*6236dae4SAndroid Build Coastguard Worker    def _mkpath(self, path):
138*6236dae4SAndroid Build Coastguard Worker        if not os.path.exists(path):
139*6236dae4SAndroid Build Coastguard Worker            return os.makedirs(path)
140*6236dae4SAndroid Build Coastguard Worker
141*6236dae4SAndroid Build Coastguard Worker    def _write_config(self):
142*6236dae4SAndroid Build Coastguard Worker        domain1 = self.env.domain1
143*6236dae4SAndroid Build Coastguard Worker        creds1 = self.env.get_credentials(domain1)
144*6236dae4SAndroid Build Coastguard Worker        assert creds1  # convince pytype this isn't None
145*6236dae4SAndroid Build Coastguard Worker        domain2 = self.env.domain2
146*6236dae4SAndroid Build Coastguard Worker        creds2 = self.env.get_credentials(domain2)
147*6236dae4SAndroid Build Coastguard Worker        assert creds2  # convince pytype this isn't None
148*6236dae4SAndroid Build Coastguard Worker        self._mkpath(self._docs_dir)
149*6236dae4SAndroid Build Coastguard Worker        self._mkpath(self._tmp_dir)
150*6236dae4SAndroid Build Coastguard Worker        with open(os.path.join(self._docs_dir, 'data.json'), 'w') as fd:
151*6236dae4SAndroid Build Coastguard Worker            data = {
152*6236dae4SAndroid Build Coastguard Worker                'server': f'{domain1}',
153*6236dae4SAndroid Build Coastguard Worker            }
154*6236dae4SAndroid Build Coastguard Worker            fd.write(JSONEncoder().encode(data))
155*6236dae4SAndroid Build Coastguard Worker        with open(self._conf_file, 'w') as fd:
156*6236dae4SAndroid Build Coastguard Worker            conf = [   # base server config
157*6236dae4SAndroid Build Coastguard Worker                '{',
158*6236dae4SAndroid Build Coastguard Worker                f'  http_port {self.env.caddy_http_port}',
159*6236dae4SAndroid Build Coastguard Worker                f'  https_port {self.env.caddy_https_port}',
160*6236dae4SAndroid Build Coastguard Worker                f'  servers :{self.env.caddy_https_port} {{',
161*6236dae4SAndroid Build Coastguard Worker                '    protocols h3 h2 h1',
162*6236dae4SAndroid Build Coastguard Worker                '  }',
163*6236dae4SAndroid Build Coastguard Worker                '}',
164*6236dae4SAndroid Build Coastguard Worker                f'{domain1}:{self.env.caddy_https_port} {{',
165*6236dae4SAndroid Build Coastguard Worker                '  file_server * {',
166*6236dae4SAndroid Build Coastguard Worker                f'    root {self._docs_dir}',
167*6236dae4SAndroid Build Coastguard Worker                '  }',
168*6236dae4SAndroid Build Coastguard Worker                f'  tls {creds1.cert_file} {creds1.pkey_file}',
169*6236dae4SAndroid Build Coastguard Worker                '}',
170*6236dae4SAndroid Build Coastguard Worker                f'{domain2} {{',
171*6236dae4SAndroid Build Coastguard Worker                f'  reverse_proxy /* http://localhost:{self.env.http_port} {{',
172*6236dae4SAndroid Build Coastguard Worker                '  }',
173*6236dae4SAndroid Build Coastguard Worker                f'  tls {creds2.cert_file} {creds2.pkey_file}',
174*6236dae4SAndroid Build Coastguard Worker                '}',
175*6236dae4SAndroid Build Coastguard Worker            ]
176*6236dae4SAndroid Build Coastguard Worker            fd.write("\n".join(conf))
177