#!/usr/bin/env python3 """Minimal path-style S3 HEAD-only stub server, used only by test_publish.sh to test publish.sh's credential check and APK-existence check entirely offline -- no real bucket, no real credentials, nothing beyond loopback. Only implements what those two checks actually call: HEAD / -- head-bucket, used to verify credentials HEAD // -- head-object, used to check for an existing APK `aws`'s S3 client only uses path-style addressing (rather than `.` virtual-hosted style) when the endpoint host is a literal IP address, which `--endpoint-url http://127.0.0.1:` is -- so no `--endpoint-url` or config trickery is needed to get requests shaped this way. Existing objects are given on the command line; every other key 404s, as does any bucket name other than the one given -- so a test that passes the wrong bucket name notices, rather than getting a silent "yes" back. Deliberately never handles PUT/GET/LIST: publish.sh must never reach the `aws s3 sync` calls in a test that uses this stub, since both a rejected credential and an existing APK are supposed to abort before any upload is attempted. If a future test needs sync to succeed against this stub, that is new scope for this file, not an oversight here. test_s3_stub.py [ ...] Prints the bound port as the one line on stdout, then serves until killed. """ import http.server import socketserver import sys def main(): bucket = sys.argv[1] existing = set(sys.argv[2:]) class Handler(http.server.BaseHTTPRequestHandler): def do_HEAD(self): path = self.path.lstrip("/") parts = path.split("/", 1) if not parts or parts[0] != bucket: self.send_response(404) self.end_headers() return if len(parts) == 1 or parts[1] == "": # head-bucket: the one bucket named on the command line # always "exists" and is reachable with these "credentials". self.send_response(200) self.end_headers() return key = parts[1] self.send_response(200 if key in existing else 404) self.end_headers() def log_message(self, fmt, *args): pass # keep test output quiet; nothing here is diagnostic class Server(socketserver.TCPServer): allow_reuse_address = True with Server(("127.0.0.1", 0), Handler) as httpd: print(httpd.server_address[1], flush=True) httpd.serve_forever() if __name__ == "__main__": main()