Repository navigation
Expand file tree
/
Copy pathmount_buckets.py
More file actions
157 lines (143 loc) · 5.02 KB
/
Copy pathmount_buckets.py
File metadata and controls
157 lines (143 loc) · 5.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"""Mount buckets with s3fs and run a file workload inside the sandbox."""
import argparse
import json
import os
import shutil
import subprocess
import sys
import tempfile
import time
import uuid
from contextlib import ExitStack, contextmanager
from itertools import islice
from pathlib import Path
try:
from .config import Bucket
except ImportError:
from config import Bucket
def workload_env() -> dict[str, str]:
return {
key: value
for key, value in os.environ.items()
if not key.startswith(("AWS_", "S3_", "R2_", "RENDER_"))
}
def mount_command(bucket: Bucket, path: Path, read_only: bool) -> list[str]:
args = [
"s3fs",
bucket.name,
str(path),
"-f",
"-o",
f"url={bucket.endpoint}",
"-o",
f"endpoint={bucket.region}",
"-o",
"use_path_request_style",
"-o",
"listobjectsv2",
"-o",
"ro" if read_only else "rw",
"-o",
"nosuid,nodev",
]
if bucket.provider == "r2":
args.extend(["-o", "nomixupload"])
if bucket.session_token:
args.extend(["-o", "use_session_token"])
return args
@contextmanager
def mount(bucket: Bucket, *, read_only: bool):
path = Path("/mnt") / bucket.provider
path.mkdir(parents=True, exist_ok=True)
if os.path.ismount(path) or any(path.iterdir()):
raise RuntimeError(f"The mount path {path} must be empty and unmounted.")
env = workload_env()
env.update(
AWS_ACCESS_KEY_ID=bucket.access_key_id,
AWS_SECRET_ACCESS_KEY=bucket.secret_access_key,
)
if bucket.session_token:
env["AWS_SESSION_TOKEN"] = bucket.session_token
with tempfile.TemporaryFile(mode="w+") as log:
process = subprocess.Popen(
mount_command(bucket, path, read_only),
env=env,
stdout=log,
stderr=log,
)
try:
deadline = time.monotonic() + 45
while not os.path.ismount(path):
if process.poll() is not None or time.monotonic() >= deadline:
log.seek(0)
details = log.read()[-4000:]
raise RuntimeError(f"Could not mount {bucket.provider}: {details}")
time.sleep(0.1)
print(f"Mounted {bucket.provider} at {path}.", flush=True)
yield path
finally:
try:
if os.path.ismount(path):
subprocess.run(["umount", str(path)], check=True, timeout=30)
finally:
if process.poll() is None:
process.terminate()
try:
process.wait(timeout=10)
except subprocess.TimeoutExpired:
process.kill()
process.wait(timeout=5)
def verify_write(bucket: Bucket) -> None:
name = f"render-sandbox-demo-{uuid.uuid4().hex}.txt"
payload = f"Hello from a Render Sandbox using {bucket.provider}!\n"
pending = False
try:
with mount(bucket, read_only=False) as path:
with (path / name).open("x") as output:
pending = True
output.write(payload)
with mount(bucket, read_only=False) as path:
if (path / name).read_text() != payload:
raise RuntimeError(
f"The {bucket.provider} file did not match after remount."
)
(path / name).unlink()
pending = False
print(f"Verified {bucket.provider}: write, remount, read, delete.", flush=True)
finally:
if pending:
try:
with mount(bucket, read_only=False) as path:
(path / name).unlink(missing_ok=True)
except Exception:
print(
f"Remove {name} from {bucket.name} if it still exists.",
file=sys.stderr,
)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--write-demo", action="store_true")
parser.add_argument("--command")
args = parser.parse_args()
config = Path(__file__).with_name("buckets.json")
try:
buckets = [Bucket(**item) for item in json.loads(config.read_text())]
finally:
config.unlink(missing_ok=True)
if not shutil.which("s3fs") or not Path("/dev/fuse").is_char_device():
raise RuntimeError("Install s3fs and use a Sandbox guest with /dev/fuse.")
with ExitStack() as stack:
for bucket in buckets:
path = stack.enter_context(mount(bucket, read_only=not args.write_demo))
print(f"First 10 entries in {path}:", flush=True)
for entry in islice(path.iterdir(), 10):
print(f" {entry.name}", flush=True)
if args.command is not None:
subprocess.run(
["bash", "-lc", args.command], env=workload_env(), check=True
)
if args.write_demo:
for bucket in buckets:
verify_write(bucket)
if __name__ == "__main__":
main()