evalmc 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- evalmc/__init__.py +0 -0
- evalmc/__main__.py +4 -0
- evalmc/check_challenge.py +124 -0
- evalmc/check_registered.py +50 -0
- evalmc/host.py +74 -0
- evalmc/login.py +124 -0
- evalmc/main.py +39 -0
- evalmc/push_helpers.py +136 -0
- evalmc/register.py +47 -0
- evalmc/set_token.py +56 -0
- evalmc/status.py +63 -0
- evalmc/submission.py +558 -0
- evalmc/submit.py +88 -0
- evalmc/submit_onnx.py +62 -0
- evalmc/utils.py +220 -0
- evalmc-0.0.1.dist-info/METADATA +89 -0
- evalmc-0.0.1.dist-info/RECORD +21 -0
- evalmc-0.0.1.dist-info/WHEEL +5 -0
- evalmc-0.0.1.dist-info/entry_points.txt +2 -0
- evalmc-0.0.1.dist-info/licenses/LICENSE +190 -0
- evalmc-0.0.1.dist-info/top_level.txt +1 -0
evalmc/status.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MyoChallenge Status - Show configuration status
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
# Configuration file paths
|
|
12
|
+
CONFIG_DIR = Path.home() / ".myochallenge"
|
|
13
|
+
HOST_URL_FILE = CONFIG_DIR / "host_url"
|
|
14
|
+
AUTH_TOKEN_FILE = CONFIG_DIR / "auth_token.json"
|
|
15
|
+
|
|
16
|
+
# Ensure config directory exists
|
|
17
|
+
CONFIG_DIR.mkdir(exist_ok=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.command()
|
|
21
|
+
def status():
|
|
22
|
+
"""
|
|
23
|
+
Show current configuration status.
|
|
24
|
+
"""
|
|
25
|
+
click.echo(click.style("MyoChallenge CLI Configuration", bold=True))
|
|
26
|
+
click.echo("=" * 40)
|
|
27
|
+
|
|
28
|
+
# Host status
|
|
29
|
+
if HOST_URL_FILE.exists():
|
|
30
|
+
try:
|
|
31
|
+
host_url = HOST_URL_FILE.read_text().strip()
|
|
32
|
+
click.echo(f"š Host URL: {host_url}")
|
|
33
|
+
except (OSError, IOError) as e:
|
|
34
|
+
click.echo(f"ā Error reading host URL: {e}")
|
|
35
|
+
else:
|
|
36
|
+
click.echo(
|
|
37
|
+
"š Host URL: Not configured (using default: https://api.mc.myosuite.org)"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Auth status
|
|
41
|
+
if AUTH_TOKEN_FILE.exists():
|
|
42
|
+
try:
|
|
43
|
+
with open(AUTH_TOKEN_FILE, "r") as f:
|
|
44
|
+
auth_data = json.load(f)
|
|
45
|
+
if auth_data.get("email") == "manual_set":
|
|
46
|
+
click.echo("š Authentication: Token manually set")
|
|
47
|
+
click.echo("š” Use 'python login.py' to set full credentials")
|
|
48
|
+
else:
|
|
49
|
+
click.echo(f"š Authenticated as: {auth_data.get('email', 'Unknown')}")
|
|
50
|
+
click.echo(f"š„ Team: {auth_data.get('team_name', 'Unknown')}")
|
|
51
|
+
click.echo(f"š User ID: {auth_data.get('user_id', 'Unknown')}")
|
|
52
|
+
except (OSError, IOError, json.JSONDecodeError) as e:
|
|
53
|
+
click.echo(f"ā Error reading auth data: {e}")
|
|
54
|
+
else:
|
|
55
|
+
click.echo("š Authentication: Not configured")
|
|
56
|
+
|
|
57
|
+
click.echo("\nš” Use 'python host.py --set-host URL' to configure host")
|
|
58
|
+
click.echo("š” Use 'python login.py' to authenticate")
|
|
59
|
+
click.echo("š” Use 'python set_token.py TOKEN' to set token directly")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
status()
|
evalmc/submission.py
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MyoChallenge Submission Script - ECR-based Docker image submission
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import uuid
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
import docker
|
|
13
|
+
import requests
|
|
14
|
+
from docker.errors import ImageNotFound
|
|
15
|
+
|
|
16
|
+
from .login import get_default_host, get_stored_auth
|
|
17
|
+
from .push_helpers import (
|
|
18
|
+
POLICY_PATHS,
|
|
19
|
+
http_error_detail,
|
|
20
|
+
image_not_found_help,
|
|
21
|
+
is_local_api,
|
|
22
|
+
local_push_remote_warning,
|
|
23
|
+
policy_missing_help,
|
|
24
|
+
post_submit_local_remote_note,
|
|
25
|
+
upload_mode_summary,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Configuration file paths
|
|
29
|
+
CONFIG_DIR = Path.home() / ".myochallenge"
|
|
30
|
+
HOST_URL_FILE = CONFIG_DIR / "host_url"
|
|
31
|
+
AUTH_TOKEN_FILE = CONFIG_DIR / "auth_token.json"
|
|
32
|
+
|
|
33
|
+
# Ensure config directory exists
|
|
34
|
+
CONFIG_DIR.mkdir(exist_ok=True)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Configuration
|
|
38
|
+
LOCAL_DOCKER_REGISTRY_URI = "localhost:5000"
|
|
39
|
+
AWS_REGION = "us-east-1"
|
|
40
|
+
ENVIRONMENT = "DEVELOPMENT" # Change to "DEVELOPMENT" for local testing
|
|
41
|
+
|
|
42
|
+
ELBOW_AGENT_BUILD_HINT = (
|
|
43
|
+
"cd mcEvalProd/evaluation/elbowflexion/agent && docker build -t my-elbow-agent:latest ."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _fail(message: str, hint: str | None = None) -> None:
|
|
48
|
+
click.echo(f"ā {message}", err=True)
|
|
49
|
+
if hint:
|
|
50
|
+
click.echo(f"\n{hint}", err=True)
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _validate_agent_image(docker_client, image: str) -> None:
|
|
55
|
+
"""Ensure the image ships policy.py or model.onnx under /policy or /app."""
|
|
56
|
+
container = None
|
|
57
|
+
try:
|
|
58
|
+
container = docker_client.containers.create(
|
|
59
|
+
image, entrypoint="sleep", command=["infinity"]
|
|
60
|
+
)
|
|
61
|
+
container.start()
|
|
62
|
+
for path in POLICY_PATHS:
|
|
63
|
+
try:
|
|
64
|
+
container.get_archive(path)
|
|
65
|
+
click.echo(f"ā
Policy artifact found in image: {path}")
|
|
66
|
+
return
|
|
67
|
+
except docker.errors.NotFound:
|
|
68
|
+
continue
|
|
69
|
+
_fail(
|
|
70
|
+
"Agent image failed policy contract check",
|
|
71
|
+
policy_missing_help(image),
|
|
72
|
+
)
|
|
73
|
+
except docker.errors.APIError as e:
|
|
74
|
+
_fail(f"Could not inspect agent image '{image}': {e}")
|
|
75
|
+
finally:
|
|
76
|
+
if container is not None:
|
|
77
|
+
try:
|
|
78
|
+
container.stop(timeout=5)
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
try:
|
|
82
|
+
container.remove(force=True)
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _run_subprocess(cmd: list[str], step: str) -> None:
|
|
88
|
+
click.echo(f"š {step}: {' '.join(cmd)}")
|
|
89
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
90
|
+
if result.returncode != 0:
|
|
91
|
+
output = (result.stderr or result.stdout or "").strip()
|
|
92
|
+
_fail(
|
|
93
|
+
f"{step} failed (exit code {result.returncode})",
|
|
94
|
+
output or "No command output captured.",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@click.command()
|
|
99
|
+
@click.argument("IMAGE", nargs=1)
|
|
100
|
+
@click.option(
|
|
101
|
+
"-p",
|
|
102
|
+
"--phase",
|
|
103
|
+
help="challenge phase number (e.g., 1, 2, 3)",
|
|
104
|
+
required=True,
|
|
105
|
+
type=int,
|
|
106
|
+
)
|
|
107
|
+
@click.option(
|
|
108
|
+
"-t",
|
|
109
|
+
"--task",
|
|
110
|
+
help="challenge task name (e.g.,tabletennis, soccer)",
|
|
111
|
+
required=True,
|
|
112
|
+
)
|
|
113
|
+
@click.option(
|
|
114
|
+
"-e",
|
|
115
|
+
"--email",
|
|
116
|
+
help="team email address for authentication",
|
|
117
|
+
required=False,
|
|
118
|
+
)
|
|
119
|
+
@click.option(
|
|
120
|
+
"-a",
|
|
121
|
+
"--auth-token",
|
|
122
|
+
help="team authentication token (password)",
|
|
123
|
+
required=False,
|
|
124
|
+
)
|
|
125
|
+
@click.option(
|
|
126
|
+
"-u",
|
|
127
|
+
"--url",
|
|
128
|
+
help="Docker Registry URI where image will be pushed (for dev/test)",
|
|
129
|
+
required=False,
|
|
130
|
+
default=LOCAL_DOCKER_REGISTRY_URI,
|
|
131
|
+
)
|
|
132
|
+
@click.option("--public", is_flag=True, help="Make submission public")
|
|
133
|
+
@click.option("--private", is_flag=True, help="Make submission private")
|
|
134
|
+
@click.option(
|
|
135
|
+
"--api-base",
|
|
136
|
+
help="API base URL",
|
|
137
|
+
default="https://api.mc.myosuite.org",
|
|
138
|
+
show_default=True,
|
|
139
|
+
)
|
|
140
|
+
@click.option(
|
|
141
|
+
"--skip-policy-check",
|
|
142
|
+
is_flag=True,
|
|
143
|
+
help="Skip verifying /policy/policy.py or model.onnx inside the image",
|
|
144
|
+
)
|
|
145
|
+
@click.option(
|
|
146
|
+
"--yes",
|
|
147
|
+
"-y",
|
|
148
|
+
is_flag=True,
|
|
149
|
+
help="Skip confirmation when remote API uses local Docker tagging (likely to fail eval)",
|
|
150
|
+
)
|
|
151
|
+
def push(image, phase, task, email, auth_token, url, public, private, api_base, skip_policy_check, yes):
|
|
152
|
+
"""
|
|
153
|
+
Submit a Docker image to the MyoChallenge competition.
|
|
154
|
+
|
|
155
|
+
IMAGE should be in the format 'name:tag' (e.g., 'myochallenge-test:latest')
|
|
156
|
+
"""
|
|
157
|
+
# Validate image format
|
|
158
|
+
if len(image.split(":")) != 2:
|
|
159
|
+
click.echo(
|
|
160
|
+
"\nā Error: Please enter the tag name with image.\n\n"
|
|
161
|
+
"For example: `python submission.py ubuntu:latest -p 1 -t manipulation "
|
|
162
|
+
"-e team@example.com -a token123`",
|
|
163
|
+
err=True,
|
|
164
|
+
)
|
|
165
|
+
sys.exit(1)
|
|
166
|
+
|
|
167
|
+
# Validate public/private flags
|
|
168
|
+
if public and private:
|
|
169
|
+
click.echo(
|
|
170
|
+
"\nā Error: Submission can't be public and private.\n"
|
|
171
|
+
"Please select either --public or --private",
|
|
172
|
+
err=True,
|
|
173
|
+
)
|
|
174
|
+
sys.exit(1)
|
|
175
|
+
|
|
176
|
+
# Set default visibility
|
|
177
|
+
if not public and not private:
|
|
178
|
+
private = True # Default to private
|
|
179
|
+
|
|
180
|
+
api_base = get_default_host()
|
|
181
|
+
|
|
182
|
+
click.echo(f"š Starting submission for {image} to {task} phase {phase}")
|
|
183
|
+
click.echo(f"š API host: {api_base}")
|
|
184
|
+
click.echo(f"š Visibility: {'Public' if public else 'Private'}")
|
|
185
|
+
|
|
186
|
+
# Initialize Docker client
|
|
187
|
+
try:
|
|
188
|
+
docker_client = docker.from_env()
|
|
189
|
+
click.echo("ā
Connected to Docker daemon")
|
|
190
|
+
except Exception as e:
|
|
191
|
+
click.echo(f"ā Failed to connect to Docker: {e}", err=True)
|
|
192
|
+
sys.exit(1)
|
|
193
|
+
|
|
194
|
+
# Check if image exists
|
|
195
|
+
try:
|
|
196
|
+
docker_image = docker_client.images.get(image)
|
|
197
|
+
image_size = docker_image.attrs.get("VirtualSize", 0)
|
|
198
|
+
image_size_gb = image_size / (1024**3)
|
|
199
|
+
click.echo(f"ā
Found image: {image} (Size: {image_size_gb:.2f} GB)")
|
|
200
|
+
except ImageNotFound:
|
|
201
|
+
hint = image_not_found_help(
|
|
202
|
+
image,
|
|
203
|
+
ELBOW_AGENT_BUILD_HINT if task == "elbowflexion" else None,
|
|
204
|
+
)
|
|
205
|
+
_fail(f"Image '{image}' not found locally", hint)
|
|
206
|
+
|
|
207
|
+
if not skip_policy_check:
|
|
208
|
+
click.echo("š Checking agent image policy contract...")
|
|
209
|
+
_validate_agent_image(docker_client, image)
|
|
210
|
+
|
|
211
|
+
# Get authentication credentials
|
|
212
|
+
if email is None or auth_token is None:
|
|
213
|
+
stored_auth = get_stored_auth()
|
|
214
|
+
|
|
215
|
+
if stored_auth:
|
|
216
|
+
email = stored_auth["email"]
|
|
217
|
+
auth_token = stored_auth["auth_token"]
|
|
218
|
+
click.echo(f"š§ Using stored credentials for: {email}")
|
|
219
|
+
else:
|
|
220
|
+
_fail(
|
|
221
|
+
"No credentials provided and none stored",
|
|
222
|
+
"Run 'evalmc login' or pass --email and --auth-token.",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Authenticate with the API
|
|
226
|
+
click.echo("š Authenticating with MyoChallenge API...")
|
|
227
|
+
try:
|
|
228
|
+
auth_response = requests.post(
|
|
229
|
+
f"{api_base}/login",
|
|
230
|
+
json={"email": email, "auth_token": auth_token},
|
|
231
|
+
timeout=10,
|
|
232
|
+
)
|
|
233
|
+
auth_response.raise_for_status()
|
|
234
|
+
auth_data = auth_response.json()
|
|
235
|
+
if not auth_data.get("success"):
|
|
236
|
+
_fail(
|
|
237
|
+
f"Authentication failed: {auth_data.get('message', 'Unknown error')}",
|
|
238
|
+
"Use your CLI submission token (not web password). Run: evalmc login --relogin",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
user_id = auth_data["user_id"]
|
|
242
|
+
team_name = auth_data["team_name"]
|
|
243
|
+
click.echo(
|
|
244
|
+
f"ā
Authenticated successfully as team: {team_name} (ID: {user_id})"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
except requests.exceptions.HTTPError as e:
|
|
248
|
+
detail = http_error_detail(e.response) if e.response is not None else str(e)
|
|
249
|
+
_fail(f"Authentication request failed: {detail}")
|
|
250
|
+
except requests.exceptions.RequestException as e:
|
|
251
|
+
_fail(f"Authentication request failed: {e}")
|
|
252
|
+
|
|
253
|
+
# Collect submission metadata (not used in ECR flow)
|
|
254
|
+
# submission_metadata = {
|
|
255
|
+
# "is_public": json.dumps(public),
|
|
256
|
+
# "method_name": "",
|
|
257
|
+
# "method_description": "",
|
|
258
|
+
# "project_url": "",
|
|
259
|
+
# "publication_url": "",
|
|
260
|
+
# }
|
|
261
|
+
|
|
262
|
+
# Generate unique tag for submission
|
|
263
|
+
submission_tag = str(uuid.uuid4())
|
|
264
|
+
click.echo(f"š·ļø Generated submission tag: {submission_tag}")
|
|
265
|
+
|
|
266
|
+
# Get ECR upload information
|
|
267
|
+
click.echo("š Getting ECR upload information...")
|
|
268
|
+
try:
|
|
269
|
+
ecr_response = requests.get(
|
|
270
|
+
f"{api_base}/ecr/upload-info/{team_name}/{task}/{phase}",
|
|
271
|
+
timeout=10,
|
|
272
|
+
)
|
|
273
|
+
ecr_response.raise_for_status()
|
|
274
|
+
ecr_data = ecr_response.json()
|
|
275
|
+
|
|
276
|
+
upload_type = ecr_data.get("upload_type", "unknown")
|
|
277
|
+
if upload_type == "error":
|
|
278
|
+
_fail(
|
|
279
|
+
"Server could not provide upload instructions",
|
|
280
|
+
ecr_data.get("error") or str(ecr_data),
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
click.echo(upload_mode_summary(upload_type, api_base, ecr_data.get("repository_uri")))
|
|
284
|
+
|
|
285
|
+
if upload_type == "local":
|
|
286
|
+
# Local storage mode ā tag image locally so the eval worker can resolve it
|
|
287
|
+
local_tag = f"{team_name.lower()}-{task}-p{phase}:latest"
|
|
288
|
+
if not is_local_api(api_base) and not yes:
|
|
289
|
+
click.echo(local_push_remote_warning(api_base, local_tag))
|
|
290
|
+
if not click.confirm("Continue with local Docker tag anyway?", default=False):
|
|
291
|
+
_fail(
|
|
292
|
+
"Submission cancelled",
|
|
293
|
+
"Use evalmc submit-onnx or evalmc submit (zip), or re-run with --yes.",
|
|
294
|
+
)
|
|
295
|
+
elif not is_local_api(api_base):
|
|
296
|
+
click.echo(local_push_remote_warning(api_base, local_tag))
|
|
297
|
+
|
|
298
|
+
click.echo("š¦ Tagging image for local eval worker...")
|
|
299
|
+
try:
|
|
300
|
+
docker_image.tag(local_tag)
|
|
301
|
+
click.echo(f"š·ļø Tagged image as {local_tag}")
|
|
302
|
+
except Exception as e:
|
|
303
|
+
_fail(f"Failed to tag image as {local_tag}: {e}")
|
|
304
|
+
|
|
305
|
+
if is_local_api(api_base):
|
|
306
|
+
click.echo(
|
|
307
|
+
"ā¹ļø Start the eval worker on this machine so it can find the tagged image:\n"
|
|
308
|
+
" cd mcEvalProd && python -m app.services.evaluate --watch "
|
|
309
|
+
f"--task {task} --phase {phase}"
|
|
310
|
+
)
|
|
311
|
+
else:
|
|
312
|
+
click.echo(
|
|
313
|
+
click.style(
|
|
314
|
+
"ā ļø Eval worker on the remote server will NOT have this Docker tag.",
|
|
315
|
+
fg="yellow",
|
|
316
|
+
bold=True,
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
image_uri = local_tag
|
|
321
|
+
click.echo(f"š¤ Recording local Docker submission: {image_uri}")
|
|
322
|
+
|
|
323
|
+
submit_response = requests.post(
|
|
324
|
+
f"{api_base}/ecr/submit",
|
|
325
|
+
json={
|
|
326
|
+
"team_name": team_name,
|
|
327
|
+
"task": task,
|
|
328
|
+
"phase": phase,
|
|
329
|
+
"image_uri": image_uri,
|
|
330
|
+
},
|
|
331
|
+
timeout=10,
|
|
332
|
+
)
|
|
333
|
+
try:
|
|
334
|
+
submit_response.raise_for_status()
|
|
335
|
+
except requests.exceptions.HTTPError as e:
|
|
336
|
+
_fail(
|
|
337
|
+
"Failed to record submission",
|
|
338
|
+
http_error_detail(e.response) if e.response is not None else str(e),
|
|
339
|
+
)
|
|
340
|
+
submit_data = submit_response.json()
|
|
341
|
+
|
|
342
|
+
if submit_data.get("status") == "success":
|
|
343
|
+
submission_id = submit_data.get("submission_id")
|
|
344
|
+
click.echo("ā
Local submission recorded successfully!")
|
|
345
|
+
click.echo(f" š Submission ID: {submission_id}")
|
|
346
|
+
click.echo(f" š³ Image URI: {image_uri}")
|
|
347
|
+
click.echo(" š Status: submitted")
|
|
348
|
+
if not is_local_api(api_base):
|
|
349
|
+
click.echo(post_submit_local_remote_note(submission_id, local_tag, api_base))
|
|
350
|
+
else:
|
|
351
|
+
_fail(
|
|
352
|
+
"Failed to record submission",
|
|
353
|
+
submit_data.get("message") or str(submit_data),
|
|
354
|
+
)
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
elif upload_type == "ecr_manual":
|
|
358
|
+
click.echo("š Manual ECR setup required:")
|
|
359
|
+
for instruction in ecr_data.get("instructions", []):
|
|
360
|
+
click.echo(f" {instruction}")
|
|
361
|
+
|
|
362
|
+
# Ask user if they want to proceed with manual ECR push
|
|
363
|
+
if not click.confirm("Do you want to proceed with manual ECR push?"):
|
|
364
|
+
click.echo("ā Submission cancelled")
|
|
365
|
+
sys.exit(0)
|
|
366
|
+
|
|
367
|
+
# For manual ECR, we'll just record the submission
|
|
368
|
+
image_uri = f"{ecr_data.get('repository_uri')}:{team_name}-{task}-{phase}"
|
|
369
|
+
click.echo(f"š¤ Recording submission for image: {image_uri}")
|
|
370
|
+
|
|
371
|
+
# Record the submission using the ECR submit endpoint
|
|
372
|
+
submit_response = requests.post(
|
|
373
|
+
f"{api_base}/ecr/submit",
|
|
374
|
+
json={
|
|
375
|
+
"team_name": team_name,
|
|
376
|
+
"task": task,
|
|
377
|
+
"phase": phase,
|
|
378
|
+
"image_uri": image_uri,
|
|
379
|
+
},
|
|
380
|
+
timeout=10,
|
|
381
|
+
)
|
|
382
|
+
submit_response.raise_for_status()
|
|
383
|
+
submit_data = submit_response.json()
|
|
384
|
+
|
|
385
|
+
if submit_data.get("status") == "success":
|
|
386
|
+
click.echo("ā
ECR submission recorded successfully!")
|
|
387
|
+
click.echo(f" š Submission ID: {submit_data.get('submission_id')}")
|
|
388
|
+
click.echo(f" š³ Image URI: {image_uri}")
|
|
389
|
+
click.echo(" š Status: submitted")
|
|
390
|
+
click.echo(
|
|
391
|
+
"\nā¹ļø You must docker push the image to ECR before evaluation can run.\n"
|
|
392
|
+
" If the image is not in ECR, status will become 'failed'."
|
|
393
|
+
)
|
|
394
|
+
else:
|
|
395
|
+
_fail(
|
|
396
|
+
"Failed to record submission",
|
|
397
|
+
submit_data.get("message") or str(submit_data),
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
elif upload_type == "ecr":
|
|
401
|
+
click.echo("š Using automatic ECR credentials...")
|
|
402
|
+
|
|
403
|
+
# Get ECR credentials from the response
|
|
404
|
+
username = ecr_data.get("username")
|
|
405
|
+
password = ecr_data.get("password")
|
|
406
|
+
proxy_endpoint = ecr_data.get("proxy_endpoint")
|
|
407
|
+
repo_uri = ecr_data.get("repository_uri")
|
|
408
|
+
|
|
409
|
+
if not all([username, password, proxy_endpoint, repo_uri]):
|
|
410
|
+
_fail(
|
|
411
|
+
"Missing ECR credentials from server",
|
|
412
|
+
f"Server returned: {ecr_data}",
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
import time
|
|
416
|
+
|
|
417
|
+
submission_id = str(uuid.uuid4())[
|
|
418
|
+
:8
|
|
419
|
+
] # Use first 8 chars of UUID for uniqueness
|
|
420
|
+
timestamp = int(time.time())
|
|
421
|
+
image_tag = f"{team_name}-{task}-{phase}-{submission_id}-{timestamp}"
|
|
422
|
+
full_image_uri = f"{repo_uri}:{image_tag}"
|
|
423
|
+
click.echo(f"š·ļø Tagging image as: {full_image_uri}")
|
|
424
|
+
click.echo(f"š Unique submission ID: {submission_id}")
|
|
425
|
+
click.echo(f"ā° Timestamp: {timestamp}")
|
|
426
|
+
|
|
427
|
+
try:
|
|
428
|
+
import json
|
|
429
|
+
import shutil
|
|
430
|
+
import tempfile
|
|
431
|
+
|
|
432
|
+
temp_dir = tempfile.mkdtemp()
|
|
433
|
+
dockerfile_path = f"{temp_dir}/Dockerfile"
|
|
434
|
+
|
|
435
|
+
metadata = {
|
|
436
|
+
"submission_id": submission_id,
|
|
437
|
+
"timestamp": timestamp,
|
|
438
|
+
"team_name": team_name,
|
|
439
|
+
"task": task,
|
|
440
|
+
"phase": phase,
|
|
441
|
+
"submission_time": time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
with open(dockerfile_path, "w") as f:
|
|
445
|
+
f.write(f"FROM {image}\n")
|
|
446
|
+
f.write(f"LABEL submission_metadata='{json.dumps(metadata)}'\n")
|
|
447
|
+
f.write(
|
|
448
|
+
f"RUN echo 'Submission: {submission_id} at {timestamp}' > /submission_info.txt\n"
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
click.echo("šØ Building unique submission image...")
|
|
452
|
+
_run_subprocess(
|
|
453
|
+
["docker", "build", "-t", full_image_uri, temp_dir],
|
|
454
|
+
"Docker build",
|
|
455
|
+
)
|
|
456
|
+
shutil.rmtree(temp_dir)
|
|
457
|
+
|
|
458
|
+
click.echo("š Logging into ECR...")
|
|
459
|
+
_run_subprocess(
|
|
460
|
+
[
|
|
461
|
+
"docker",
|
|
462
|
+
"login",
|
|
463
|
+
"-u",
|
|
464
|
+
username,
|
|
465
|
+
"-p",
|
|
466
|
+
password,
|
|
467
|
+
proxy_endpoint,
|
|
468
|
+
],
|
|
469
|
+
"Docker login to ECR",
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
click.echo("š¤ Pushing image to ECR...")
|
|
473
|
+
_run_subprocess(["docker", "push", full_image_uri], "Docker push to ECR")
|
|
474
|
+
click.echo("ā
Image pushed to ECR successfully!")
|
|
475
|
+
|
|
476
|
+
# Record the submission with the full ECR URI
|
|
477
|
+
image_uri = full_image_uri
|
|
478
|
+
click.echo(f"š¤ Recording submission for image: {image_uri}")
|
|
479
|
+
|
|
480
|
+
# Record the submission using the ECR submit endpoint
|
|
481
|
+
submit_response = requests.post(
|
|
482
|
+
f"{api_base}/ecr/submit",
|
|
483
|
+
json={
|
|
484
|
+
"team_name": team_name,
|
|
485
|
+
"task": task,
|
|
486
|
+
"phase": phase,
|
|
487
|
+
"image_uri": image_uri,
|
|
488
|
+
},
|
|
489
|
+
timeout=10,
|
|
490
|
+
)
|
|
491
|
+
submit_response.raise_for_status()
|
|
492
|
+
submit_data = submit_response.json()
|
|
493
|
+
|
|
494
|
+
if submit_data.get("status") == "success":
|
|
495
|
+
click.echo("ā
ECR submission recorded successfully!")
|
|
496
|
+
click.echo(
|
|
497
|
+
f" š Submission ID: {submit_data.get('submission_id')}"
|
|
498
|
+
)
|
|
499
|
+
click.echo(f" š³ Image URI: {image_uri}")
|
|
500
|
+
click.echo(" š Status: submitted")
|
|
501
|
+
else:
|
|
502
|
+
_fail(
|
|
503
|
+
"Failed to record submission",
|
|
504
|
+
submit_data.get("message") or str(submit_data),
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
except Exception as e:
|
|
508
|
+
_fail(f"Failed to push to ECR: {e}")
|
|
509
|
+
|
|
510
|
+
else:
|
|
511
|
+
_fail(
|
|
512
|
+
f"Unknown server upload mode: {upload_type!r}",
|
|
513
|
+
f"Server response: {ecr_data}",
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
except requests.exceptions.HTTPError as e:
|
|
517
|
+
detail = http_error_detail(e.response) if e.response is not None else str(e)
|
|
518
|
+
_fail(f"Failed to get ECR upload info: {detail}")
|
|
519
|
+
except requests.exceptions.RequestException as e:
|
|
520
|
+
_fail(f"Failed to get ECR upload info: {e}")
|
|
521
|
+
|
|
522
|
+
# Verify submission was recorded
|
|
523
|
+
click.echo("š Verifying submission record...")
|
|
524
|
+
try:
|
|
525
|
+
submissions_response = requests.get(
|
|
526
|
+
f"{api_base}/submissions/team/{team_name}",
|
|
527
|
+
headers={"X-User-Email": email, "X-Auth-Token": auth_token},
|
|
528
|
+
timeout=10,
|
|
529
|
+
)
|
|
530
|
+
submissions_response.raise_for_status()
|
|
531
|
+
submissions_data = submissions_response.json()
|
|
532
|
+
|
|
533
|
+
# Find our submission by task and phase
|
|
534
|
+
latest_submission = None
|
|
535
|
+
for submission in submissions_data.get("submissions", []):
|
|
536
|
+
if submission.get("task") == task and submission.get("phase") == phase:
|
|
537
|
+
latest_submission = submission
|
|
538
|
+
break
|
|
539
|
+
|
|
540
|
+
if latest_submission:
|
|
541
|
+
click.echo("ā
Submission verified successfully!")
|
|
542
|
+
click.echo(f" š Submission ID: {latest_submission.get('id')}")
|
|
543
|
+
click.echo(f" š³ ECR URI: {latest_submission.get('ecr_uri')}")
|
|
544
|
+
click.echo(f" š
Created: {latest_submission.get('created_at')}")
|
|
545
|
+
click.echo(f" š Status: {latest_submission.get('status', 'submitted')}")
|
|
546
|
+
else:
|
|
547
|
+
click.echo("ā ļø Submission may not have been recorded properly")
|
|
548
|
+
|
|
549
|
+
except requests.exceptions.RequestException as e:
|
|
550
|
+
click.echo(f"ā ļø Could not verify submission record: {e}")
|
|
551
|
+
|
|
552
|
+
click.echo("\nš Submission completed successfully!")
|
|
553
|
+
click.echo(f"š§ You will receive updates at: {email}")
|
|
554
|
+
click.echo(f"š View your submissions at: {api_base}/dashboard")
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
if __name__ == "__main__":
|
|
558
|
+
push()
|