47 lines
2.3 KiB
Python
47 lines
2.3 KiB
Python
"""Upload a tested artifact and environment over authenticated SSH; never log secrets."""
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import shlex
|
|
import subprocess
|
|
import tempfile
|
|
import uuid
|
|
|
|
|
|
def main():
|
|
required = ["DEPLOY_HOST", "DEPLOY_USER", "DEPLOY_SSH_KEY", "DEPLOY_KNOWN_HOSTS", "DEPLOY_PATH", "LOTTERY_ENV"]
|
|
for key in required:
|
|
if not os.getenv(key, "").strip():
|
|
raise SystemExit(f"Missing Drone secret/environment: {key}")
|
|
host, user = os.environ["DEPLOY_HOST"], os.environ["DEPLOY_USER"]
|
|
commit, build = os.environ["DRONE_COMMIT_SHA"], os.environ["DRONE_BUILD_NUMBER"]
|
|
if not re.fullmatch(r"[a-zA-Z0-9.-]+", host) or not re.fullmatch(r"[a-zA-Z_][a-zA-Z0-9_-]*", user):
|
|
raise SystemExit("Invalid deployment host/user")
|
|
if not re.fullmatch(r"[a-f0-9]{40}", commit) or not re.fullmatch(r"[0-9]+", build):
|
|
raise SystemExit("Invalid build identity")
|
|
destination = f"{user}@{host}"
|
|
remote = f"/tmp/lottery-deploy-{uuid.uuid4().hex}"
|
|
with tempfile.TemporaryDirectory(prefix="lottery-ssh-") as directory:
|
|
directory = Path(directory)
|
|
key, known, environment = directory / "key", directory / "known_hosts", directory / "runtime.env"
|
|
for path, value in ((key, "DEPLOY_SSH_KEY"), (known, "DEPLOY_KNOWN_HOSTS"), (environment, "LOTTERY_ENV")):
|
|
path.write_text(os.environ[value].strip() + "\n", encoding="utf-8")
|
|
path.chmod(0o600)
|
|
options = ["-i", str(key), "-o", "BatchMode=yes", "-o", "IdentitiesOnly=yes",
|
|
"-o", "StrictHostKeyChecking=yes", "-o", f"UserKnownHostsFile={known}",
|
|
"-o", "ConnectTimeout=15"]
|
|
def ssh(command):
|
|
subprocess.run(["ssh", *options, destination, command], check=True, timeout=1200)
|
|
ssh(f"umask 077; mkdir {shlex.quote(remote)}")
|
|
try:
|
|
subprocess.run(["scp", *options, "dist/lottery.tar.gz", "scripts/deploy_release.sh", str(environment),
|
|
f"{destination}:{remote}/"], check=True, timeout=300)
|
|
ssh("sh " + shlex.join([remote + "/deploy_release.sh", os.environ["DEPLOY_PATH"], remote, commit, build]))
|
|
finally:
|
|
# Remove only the exact uploaded secret, even when a deployment fails.
|
|
ssh("rm -f -- " + shlex.quote(remote + "/runtime.env"))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|