Yichong Lu
commited on
Commit
·
f2a492d
1
Parent(s):
9c3007d
add submission addition function
Browse files- competitions/app.py +7 -2
- competitions/utils.py +51 -36
competitions/app.py
CHANGED
|
@@ -30,7 +30,6 @@ from competitions.text import SUBMISSION_SELECTION_TEXT, SUBMISSION_TEXT
|
|
| 30 |
from competitions.utils import team_file_api, submission_api, leaderboard_api, error_log_api
|
| 31 |
from competitions.enums import SubmissionStatus, ErrorMessage
|
| 32 |
|
| 33 |
-
|
| 34 |
HF_TOKEN = os.environ.get("HF_TOKEN", None)
|
| 35 |
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 36 |
COMPETITION_ID = os.environ.get("COMPETITION_ID")
|
|
@@ -333,15 +332,21 @@ def new_submission(
|
|
| 333 |
if team_id not in team_file_api.get_team_white_list():
|
| 334 |
return {"response": "You are not allowed to make submissions."}
|
| 335 |
|
|
|
|
|
|
|
| 336 |
lock = FileLock(f"./submission_lock/{team_id}.lock", blocking=False)
|
| 337 |
try:
|
| 338 |
with lock:
|
| 339 |
if submission_api.exists_submission_info(team_id) and submission_api.count_by_status(team_id, [SubmissionStatus.QUEUED, SubmissionStatus.PENDING, SubmissionStatus.PROCESSING]) > 0:
|
| 340 |
return {"response": "Another submission is being processed. Please wait a moment."}
|
| 341 |
competition_info = CompetitionInfo(competition_id=COMPETITION_ID, autotrain_token=HF_TOKEN)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
sub = Submissions(
|
| 343 |
end_date=competition_info.end_date,
|
| 344 |
-
submission_limit=
|
| 345 |
competition_id=COMPETITION_ID,
|
| 346 |
token=HF_TOKEN,
|
| 347 |
competition_type=competition_info.competition_type,
|
|
|
|
| 30 |
from competitions.utils import team_file_api, submission_api, leaderboard_api, error_log_api
|
| 31 |
from competitions.enums import SubmissionStatus, ErrorMessage
|
| 32 |
|
|
|
|
| 33 |
HF_TOKEN = os.environ.get("HF_TOKEN", None)
|
| 34 |
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 35 |
COMPETITION_ID = os.environ.get("COMPETITION_ID")
|
|
|
|
| 332 |
if team_id not in team_file_api.get_team_white_list():
|
| 333 |
return {"response": "You are not allowed to make submissions."}
|
| 334 |
|
| 335 |
+
team_submission_limit_dict = team_file_api.get_team_submission_limit(user_token)
|
| 336 |
+
|
| 337 |
lock = FileLock(f"./submission_lock/{team_id}.lock", blocking=False)
|
| 338 |
try:
|
| 339 |
with lock:
|
| 340 |
if submission_api.exists_submission_info(team_id) and submission_api.count_by_status(team_id, [SubmissionStatus.QUEUED, SubmissionStatus.PENDING, SubmissionStatus.PROCESSING]) > 0:
|
| 341 |
return {"response": "Another submission is being processed. Please wait a moment."}
|
| 342 |
competition_info = CompetitionInfo(competition_id=COMPETITION_ID, autotrain_token=HF_TOKEN)
|
| 343 |
+
if team_id in team_submission_limit_dict.keys():
|
| 344 |
+
submission_limit = team_submission_limit_dict[team_id]
|
| 345 |
+
else:
|
| 346 |
+
submission_limit = competition_info.submission_limit
|
| 347 |
sub = Submissions(
|
| 348 |
end_date=competition_info.end_date,
|
| 349 |
+
submission_limit=submission_limit,
|
| 350 |
competition_id=COMPETITION_ID,
|
| 351 |
token=HF_TOKEN,
|
| 352 |
competition_type=competition_info.competition_type,
|
competitions/utils.py
CHANGED
|
@@ -28,7 +28,6 @@ from competitions.params import EvalParams
|
|
| 28 |
|
| 29 |
from . import HF_URL
|
| 30 |
|
| 31 |
-
|
| 32 |
USER_TOKEN = os.environ.get("USER_TOKEN")
|
| 33 |
|
| 34 |
|
|
@@ -269,6 +268,7 @@ class TeamAlreadyExistsError(Exception):
|
|
| 269 |
"""Custom exception for when a team already exists."""
|
| 270 |
pass
|
| 271 |
|
|
|
|
| 272 |
class TeamFileApi:
|
| 273 |
def __init__(self, hf_token: str, competition_id: str):
|
| 274 |
self.hf_token = hf_token
|
|
@@ -358,7 +358,7 @@ class TeamFileApi:
|
|
| 358 |
repo_type="dataset",
|
| 359 |
)
|
| 360 |
return team_id
|
| 361 |
-
|
| 362 |
def create_team(self, user_token: str, team_name: str, other_data: Dict[str, Any]) -> str:
|
| 363 |
user_info = token_information(token=user_token)
|
| 364 |
return self._create_team(user_info["id"], team_name, other_data)
|
|
@@ -394,7 +394,7 @@ class TeamFileApi:
|
|
| 394 |
repo_type="dataset",
|
| 395 |
)
|
| 396 |
return new_team_name
|
| 397 |
-
|
| 398 |
@cached(cache=TTLCache(maxsize=1, ttl=600))
|
| 399 |
def get_team_white_list(self) -> List[str]:
|
| 400 |
file = hf_hub_download(
|
|
@@ -408,6 +408,19 @@ class TeamFileApi:
|
|
| 408 |
team_white_list = json.load(f)
|
| 409 |
return team_white_list
|
| 410 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 411 |
|
| 412 |
team_file_api = TeamFileApi(
|
| 413 |
os.environ.get("HF_TOKEN", None),
|
|
@@ -420,7 +433,7 @@ class UserTokenApi:
|
|
| 420 |
self.hf_token = hf_token
|
| 421 |
self.key = base64.b64decode(key_base64)
|
| 422 |
self.competition_id = competition_id
|
| 423 |
-
|
| 424 |
def _encrypt(self, text: str) -> str:
|
| 425 |
aesgcm = AESGCM(self.key)
|
| 426 |
nonce = os.urandom(12)
|
|
@@ -434,7 +447,7 @@ class UserTokenApi:
|
|
| 434 |
ciphertext = data[12:]
|
| 435 |
plaintext = aesgcm.decrypt(nonce, ciphertext, None)
|
| 436 |
return plaintext.decode()
|
| 437 |
-
|
| 438 |
def put(self, team_id: str, user_token: str):
|
| 439 |
encrypted_token = self._encrypt(user_token)
|
| 440 |
api = HfApi(token=self.hf_token)
|
|
@@ -475,13 +488,13 @@ class ServerManager:
|
|
| 475 |
self.server_url_list = server_url_list
|
| 476 |
self._cur_index = 0
|
| 477 |
self._lock = threading.Lock()
|
| 478 |
-
|
| 479 |
def get_next_server(self) -> str:
|
| 480 |
with self._lock:
|
| 481 |
server_url = self.server_url_list[self._cur_index]
|
| 482 |
self._cur_index = (self._cur_index + 1) % len(self.server_url_list)
|
| 483 |
return server_url
|
| 484 |
-
|
| 485 |
|
| 486 |
server_manager = ServerManager(["https://xdimlab-hugsim-web-server-0.hf.space"])
|
| 487 |
|
|
@@ -491,7 +504,7 @@ class SubmissionApi:
|
|
| 491 |
self.hf_token = hf_token
|
| 492 |
self.competition_id = competition_id
|
| 493 |
self.api = HfApi(token=hf_token)
|
| 494 |
-
|
| 495 |
def exists_submission_info(self, team_id: str) -> bool:
|
| 496 |
"""
|
| 497 |
Check if submission info exists for a given team ID.
|
|
@@ -521,7 +534,7 @@ class SubmissionApi:
|
|
| 521 |
)
|
| 522 |
with open(submission_info_path, 'r') as f:
|
| 523 |
submission_info = json.load(f)
|
| 524 |
-
|
| 525 |
return submission_info
|
| 526 |
|
| 527 |
def upload_submission_info(self, team_id: str, user_submission_info: Dict[str, Any]):
|
|
@@ -545,10 +558,11 @@ class SubmissionApi:
|
|
| 545 |
|
| 546 |
def update_submission_status(self, team_id: str, submission_id: str, status: int):
|
| 547 |
self.update_submission_data(team_id, submission_id, {"status": status})
|
| 548 |
-
|
| 549 |
def count_by_status(self, team_id: str, status_list: List[SubmissionStatus]) -> int:
|
| 550 |
user_submission_info = self.download_submission_info(team_id)
|
| 551 |
-
count = sum(1 for submission in user_submission_info["submissions"] if
|
|
|
|
| 552 |
return count
|
| 553 |
|
| 554 |
|
|
@@ -600,7 +614,7 @@ class ErrorLogApi:
|
|
| 600 |
}
|
| 601 |
token = jwt.encode(payload, self.encode_key, algorithm="HS256")
|
| 602 |
return token
|
| 603 |
-
|
| 604 |
def get_log_by_token(self, token: str) -> str:
|
| 605 |
try:
|
| 606 |
payload = jwt.decode(token, self.encode_key, algorithms=["HS256"])
|
|
@@ -617,7 +631,7 @@ class ErrorLogApi:
|
|
| 617 |
)
|
| 618 |
with open(log_file_path, 'r') as f:
|
| 619 |
file_content = f.read()
|
| 620 |
-
|
| 621 |
return file_content
|
| 622 |
|
| 623 |
|
|
@@ -669,12 +683,13 @@ class SpaceCleaner:
|
|
| 669 |
submission_api.update_submission_data(
|
| 670 |
team_id=team_id,
|
| 671 |
submission_id=submission_id,
|
| 672 |
-
data={"status": SubmissionStatus.FAILED.value,
|
|
|
|
| 673 |
)
|
| 674 |
else:
|
| 675 |
self.api.restart_space(repo_id=space_id)
|
| 676 |
return
|
| 677 |
-
|
| 678 |
if space_info.runtime.stage == SpaceStage.RUNTIME_ERROR:
|
| 679 |
log_content = error_log_api.get_log(space_id, kind="run")
|
| 680 |
error_log_api.save_error_log(submission_id, log_content)
|
|
@@ -757,7 +772,6 @@ leaderboard_api = LeaderboardApi(
|
|
| 757 |
)
|
| 758 |
|
| 759 |
|
| 760 |
-
|
| 761 |
class DockerfileModifier:
|
| 762 |
def __init__(self, allowed_hosts: str, source_so_path: str = "./network_filter.so"):
|
| 763 |
self.allowed_hosts = allowed_hosts
|
|
@@ -765,46 +779,46 @@ class DockerfileModifier:
|
|
| 765 |
self.tatget_so_dir = "/_app_extensions"
|
| 766 |
self.tatget_so_path = os.path.join(self.tatget_so_dir, "network_filter.so")
|
| 767 |
self.preload_prefix = f'LD_PRELOAD={self.tatget_so_path} ALLOWED_HOSTS="{allowed_hosts}"'
|
| 768 |
-
|
| 769 |
def parse_dockerfile_line(self, line: str) -> Tuple[str, str, str]:
|
| 770 |
"""
|
| 771 |
解析 Dockerfile 行,返回 (指令名, 原始命令, 格式类型)
|
| 772 |
格式类型: 'exec' (JSON数组) 或 'shell' (shell命令)
|
| 773 |
"""
|
| 774 |
line = line.strip()
|
| 775 |
-
|
| 776 |
# 匹配 CMD 或 ENTRYPOINT
|
| 777 |
cmd_match = re.match(r'^(CMD|ENTRYPOINT)\s+(.+)$', line, re.IGNORECASE)
|
| 778 |
if not cmd_match:
|
| 779 |
return "", "", ""
|
| 780 |
-
|
| 781 |
instruction = cmd_match.group(1).upper()
|
| 782 |
command_part = cmd_match.group(2).strip()
|
| 783 |
-
|
| 784 |
# 判断是 exec 格式 (JSON数组) 还是 shell 格式
|
| 785 |
if command_part.startswith('[') and command_part.endswith(']'):
|
| 786 |
return instruction, command_part, "exec"
|
| 787 |
else:
|
| 788 |
return instruction, command_part, "shell"
|
| 789 |
-
|
| 790 |
def modify_shell_format(self, command: str) -> str:
|
| 791 |
"""修改 shell 格式的命令"""
|
| 792 |
# 在原命令前添加环境变量
|
| 793 |
return f'{self.preload_prefix} {command}'
|
| 794 |
-
|
| 795 |
def modify_exec_format(self, command: str) -> str:
|
| 796 |
"""修改 exec 格式 (JSON数组) 的命令"""
|
| 797 |
try:
|
| 798 |
# 解析 JSON 数组格式
|
| 799 |
# 移除外层的方括号
|
| 800 |
inner = command[1:-1].strip()
|
| 801 |
-
|
| 802 |
# 简单的 JSON 数组解析
|
| 803 |
parts = []
|
| 804 |
current = ""
|
| 805 |
in_quotes = False
|
| 806 |
escape_next = False
|
| 807 |
-
|
| 808 |
for char in inner:
|
| 809 |
if escape_next:
|
| 810 |
current += char
|
|
@@ -820,10 +834,10 @@ class DockerfileModifier:
|
|
| 820 |
current = ""
|
| 821 |
else:
|
| 822 |
current += char
|
| 823 |
-
|
| 824 |
if current.strip():
|
| 825 |
parts.append(current.strip())
|
| 826 |
-
|
| 827 |
# 移除引号并处理转义
|
| 828 |
cleaned_parts = []
|
| 829 |
for part in parts:
|
|
@@ -833,10 +847,10 @@ class DockerfileModifier:
|
|
| 833 |
# 处理基本的转义字符
|
| 834 |
part = part.replace('\\"', '"').replace('\\\\', '\\')
|
| 835 |
cleaned_parts.append(part)
|
| 836 |
-
|
| 837 |
if not cleaned_parts:
|
| 838 |
return command
|
| 839 |
-
|
| 840 |
# 构建新的命令
|
| 841 |
# 第一个元素通常是 shell (/bin/sh, /bin/bash 等)
|
| 842 |
# 如果第一个元素是 shell,修改执行的命令
|
|
@@ -855,22 +869,22 @@ class DockerfileModifier:
|
|
| 855 |
# 直接执行的命令,需要通过 shell 包装
|
| 856 |
original_cmd = ' '.join(cleaned_parts)
|
| 857 |
new_parts = ['/bin/sh', '-c', f'{self.preload_prefix} {original_cmd}']
|
| 858 |
-
|
| 859 |
# 重新构建 JSON 数组
|
| 860 |
escaped_parts = []
|
| 861 |
for part in new_parts:
|
| 862 |
# 转义引号和反斜杠
|
| 863 |
escaped = part.replace('\\', '\\\\').replace('"', '\\"')
|
| 864 |
escaped_parts.append(f'"{escaped}"')
|
| 865 |
-
|
| 866 |
return '[' + ', '.join(escaped_parts) + ']'
|
| 867 |
-
|
| 868 |
except Exception as e:
|
| 869 |
print(f"警告: 解析 exec 格式失败: {e}")
|
| 870 |
print(f"原始命令: {command}")
|
| 871 |
# 如果解析失败,转换为 shell 格式
|
| 872 |
return f'{self.preload_prefix} {command}'
|
| 873 |
-
|
| 874 |
def modify_dockerfile_content(self, content: str) -> Tuple[str, List[str]]:
|
| 875 |
"""
|
| 876 |
修改 Dockerfile 内容
|
|
@@ -879,10 +893,10 @@ class DockerfileModifier:
|
|
| 879 |
lines = content.splitlines()
|
| 880 |
modified_lines = []
|
| 881 |
changes = []
|
| 882 |
-
|
| 883 |
for i, line in enumerate(lines, 1):
|
| 884 |
instruction, command, format_type = self.parse_dockerfile_line(line)
|
| 885 |
-
|
| 886 |
if instruction in ['CMD', 'ENTRYPOINT'] and command:
|
| 887 |
if format_type == "shell":
|
| 888 |
new_command = self.modify_shell_format(command)
|
|
@@ -892,7 +906,7 @@ class DockerfileModifier:
|
|
| 892 |
new_line = f'{instruction} {new_command}'
|
| 893 |
else:
|
| 894 |
new_line = line
|
| 895 |
-
|
| 896 |
changes.append(f"第 {i} 行: {instruction} 指令已修改")
|
| 897 |
changes.append(f" 原始: {line}")
|
| 898 |
changes.append(f" 修改: {new_line}")
|
|
@@ -908,7 +922,8 @@ class DockerfileModifier:
|
|
| 908 |
if last_user is None:
|
| 909 |
modified_lines.insert(-1, f"COPY {self.source_so_path}" + f" {self.tatget_so_path}")
|
| 910 |
else:
|
| 911 |
-
modified_lines.insert(-1,
|
|
|
|
| 912 |
modified_lines.insert(-1, f"RUN chown -R {last_user}:{last_user} {self.tatget_so_dir}")
|
| 913 |
|
| 914 |
return '\n'.join(modified_lines), changes
|
|
|
|
| 28 |
|
| 29 |
from . import HF_URL
|
| 30 |
|
|
|
|
| 31 |
USER_TOKEN = os.environ.get("USER_TOKEN")
|
| 32 |
|
| 33 |
|
|
|
|
| 268 |
"""Custom exception for when a team already exists."""
|
| 269 |
pass
|
| 270 |
|
| 271 |
+
|
| 272 |
class TeamFileApi:
|
| 273 |
def __init__(self, hf_token: str, competition_id: str):
|
| 274 |
self.hf_token = hf_token
|
|
|
|
| 358 |
repo_type="dataset",
|
| 359 |
)
|
| 360 |
return team_id
|
| 361 |
+
|
| 362 |
def create_team(self, user_token: str, team_name: str, other_data: Dict[str, Any]) -> str:
|
| 363 |
user_info = token_information(token=user_token)
|
| 364 |
return self._create_team(user_info["id"], team_name, other_data)
|
|
|
|
| 394 |
repo_type="dataset",
|
| 395 |
)
|
| 396 |
return new_team_name
|
| 397 |
+
|
| 398 |
@cached(cache=TTLCache(maxsize=1, ttl=600))
|
| 399 |
def get_team_white_list(self) -> List[str]:
|
| 400 |
file = hf_hub_download(
|
|
|
|
| 408 |
team_white_list = json.load(f)
|
| 409 |
return team_white_list
|
| 410 |
|
| 411 |
+
@cached(cache=TTLCache(maxsize=1, ttl=600))
|
| 412 |
+
def get_team_submission_limit(self) -> Dict[str, str]:
|
| 413 |
+
file = hf_hub_download(
|
| 414 |
+
repo_id=self.competition_id,
|
| 415 |
+
filename="team_submission_limit.json",
|
| 416 |
+
token=self.hf_token,
|
| 417 |
+
repo_type="dataset",
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
with open(file, "r", encoding="utf-8") as f:
|
| 421 |
+
team_submission_limit = json.load(f)
|
| 422 |
+
return team_submission_limit
|
| 423 |
+
|
| 424 |
|
| 425 |
team_file_api = TeamFileApi(
|
| 426 |
os.environ.get("HF_TOKEN", None),
|
|
|
|
| 433 |
self.hf_token = hf_token
|
| 434 |
self.key = base64.b64decode(key_base64)
|
| 435 |
self.competition_id = competition_id
|
| 436 |
+
|
| 437 |
def _encrypt(self, text: str) -> str:
|
| 438 |
aesgcm = AESGCM(self.key)
|
| 439 |
nonce = os.urandom(12)
|
|
|
|
| 447 |
ciphertext = data[12:]
|
| 448 |
plaintext = aesgcm.decrypt(nonce, ciphertext, None)
|
| 449 |
return plaintext.decode()
|
| 450 |
+
|
| 451 |
def put(self, team_id: str, user_token: str):
|
| 452 |
encrypted_token = self._encrypt(user_token)
|
| 453 |
api = HfApi(token=self.hf_token)
|
|
|
|
| 488 |
self.server_url_list = server_url_list
|
| 489 |
self._cur_index = 0
|
| 490 |
self._lock = threading.Lock()
|
| 491 |
+
|
| 492 |
def get_next_server(self) -> str:
|
| 493 |
with self._lock:
|
| 494 |
server_url = self.server_url_list[self._cur_index]
|
| 495 |
self._cur_index = (self._cur_index + 1) % len(self.server_url_list)
|
| 496 |
return server_url
|
| 497 |
+
|
| 498 |
|
| 499 |
server_manager = ServerManager(["https://xdimlab-hugsim-web-server-0.hf.space"])
|
| 500 |
|
|
|
|
| 504 |
self.hf_token = hf_token
|
| 505 |
self.competition_id = competition_id
|
| 506 |
self.api = HfApi(token=hf_token)
|
| 507 |
+
|
| 508 |
def exists_submission_info(self, team_id: str) -> bool:
|
| 509 |
"""
|
| 510 |
Check if submission info exists for a given team ID.
|
|
|
|
| 534 |
)
|
| 535 |
with open(submission_info_path, 'r') as f:
|
| 536 |
submission_info = json.load(f)
|
| 537 |
+
|
| 538 |
return submission_info
|
| 539 |
|
| 540 |
def upload_submission_info(self, team_id: str, user_submission_info: Dict[str, Any]):
|
|
|
|
| 558 |
|
| 559 |
def update_submission_status(self, team_id: str, submission_id: str, status: int):
|
| 560 |
self.update_submission_data(team_id, submission_id, {"status": status})
|
| 561 |
+
|
| 562 |
def count_by_status(self, team_id: str, status_list: List[SubmissionStatus]) -> int:
|
| 563 |
user_submission_info = self.download_submission_info(team_id)
|
| 564 |
+
count = sum(1 for submission in user_submission_info["submissions"] if
|
| 565 |
+
SubmissionStatus(submission["status"]) in status_list)
|
| 566 |
return count
|
| 567 |
|
| 568 |
|
|
|
|
| 614 |
}
|
| 615 |
token = jwt.encode(payload, self.encode_key, algorithm="HS256")
|
| 616 |
return token
|
| 617 |
+
|
| 618 |
def get_log_by_token(self, token: str) -> str:
|
| 619 |
try:
|
| 620 |
payload = jwt.decode(token, self.encode_key, algorithms=["HS256"])
|
|
|
|
| 631 |
)
|
| 632 |
with open(log_file_path, 'r') as f:
|
| 633 |
file_content = f.read()
|
| 634 |
+
|
| 635 |
return file_content
|
| 636 |
|
| 637 |
|
|
|
|
| 683 |
submission_api.update_submission_data(
|
| 684 |
team_id=team_id,
|
| 685 |
submission_id=submission_id,
|
| 686 |
+
data={"status": SubmissionStatus.FAILED.value,
|
| 687 |
+
"error_message": ErrorMessage.BUILD_SPACE_FAILED.value},
|
| 688 |
)
|
| 689 |
else:
|
| 690 |
self.api.restart_space(repo_id=space_id)
|
| 691 |
return
|
| 692 |
+
|
| 693 |
if space_info.runtime.stage == SpaceStage.RUNTIME_ERROR:
|
| 694 |
log_content = error_log_api.get_log(space_id, kind="run")
|
| 695 |
error_log_api.save_error_log(submission_id, log_content)
|
|
|
|
| 772 |
)
|
| 773 |
|
| 774 |
|
|
|
|
| 775 |
class DockerfileModifier:
|
| 776 |
def __init__(self, allowed_hosts: str, source_so_path: str = "./network_filter.so"):
|
| 777 |
self.allowed_hosts = allowed_hosts
|
|
|
|
| 779 |
self.tatget_so_dir = "/_app_extensions"
|
| 780 |
self.tatget_so_path = os.path.join(self.tatget_so_dir, "network_filter.so")
|
| 781 |
self.preload_prefix = f'LD_PRELOAD={self.tatget_so_path} ALLOWED_HOSTS="{allowed_hosts}"'
|
| 782 |
+
|
| 783 |
def parse_dockerfile_line(self, line: str) -> Tuple[str, str, str]:
|
| 784 |
"""
|
| 785 |
解析 Dockerfile 行,返回 (指令名, 原始命令, 格式类型)
|
| 786 |
格式类型: 'exec' (JSON数组) 或 'shell' (shell命令)
|
| 787 |
"""
|
| 788 |
line = line.strip()
|
| 789 |
+
|
| 790 |
# 匹配 CMD 或 ENTRYPOINT
|
| 791 |
cmd_match = re.match(r'^(CMD|ENTRYPOINT)\s+(.+)$', line, re.IGNORECASE)
|
| 792 |
if not cmd_match:
|
| 793 |
return "", "", ""
|
| 794 |
+
|
| 795 |
instruction = cmd_match.group(1).upper()
|
| 796 |
command_part = cmd_match.group(2).strip()
|
| 797 |
+
|
| 798 |
# 判断是 exec 格式 (JSON数组) 还是 shell 格式
|
| 799 |
if command_part.startswith('[') and command_part.endswith(']'):
|
| 800 |
return instruction, command_part, "exec"
|
| 801 |
else:
|
| 802 |
return instruction, command_part, "shell"
|
| 803 |
+
|
| 804 |
def modify_shell_format(self, command: str) -> str:
|
| 805 |
"""修改 shell 格式的命令"""
|
| 806 |
# 在原命令前添加环境变量
|
| 807 |
return f'{self.preload_prefix} {command}'
|
| 808 |
+
|
| 809 |
def modify_exec_format(self, command: str) -> str:
|
| 810 |
"""修改 exec 格式 (JSON数组) 的命令"""
|
| 811 |
try:
|
| 812 |
# 解析 JSON 数组格式
|
| 813 |
# 移除外层的方括号
|
| 814 |
inner = command[1:-1].strip()
|
| 815 |
+
|
| 816 |
# 简单的 JSON 数组解析
|
| 817 |
parts = []
|
| 818 |
current = ""
|
| 819 |
in_quotes = False
|
| 820 |
escape_next = False
|
| 821 |
+
|
| 822 |
for char in inner:
|
| 823 |
if escape_next:
|
| 824 |
current += char
|
|
|
|
| 834 |
current = ""
|
| 835 |
else:
|
| 836 |
current += char
|
| 837 |
+
|
| 838 |
if current.strip():
|
| 839 |
parts.append(current.strip())
|
| 840 |
+
|
| 841 |
# 移除引号并处理转义
|
| 842 |
cleaned_parts = []
|
| 843 |
for part in parts:
|
|
|
|
| 847 |
# 处理基本的转义字符
|
| 848 |
part = part.replace('\\"', '"').replace('\\\\', '\\')
|
| 849 |
cleaned_parts.append(part)
|
| 850 |
+
|
| 851 |
if not cleaned_parts:
|
| 852 |
return command
|
| 853 |
+
|
| 854 |
# 构建新的命令
|
| 855 |
# 第一个元素通常是 shell (/bin/sh, /bin/bash 等)
|
| 856 |
# 如果第一个元素是 shell,修改执行的命令
|
|
|
|
| 869 |
# 直接执行的命令,需要通过 shell 包装
|
| 870 |
original_cmd = ' '.join(cleaned_parts)
|
| 871 |
new_parts = ['/bin/sh', '-c', f'{self.preload_prefix} {original_cmd}']
|
| 872 |
+
|
| 873 |
# 重新构建 JSON 数组
|
| 874 |
escaped_parts = []
|
| 875 |
for part in new_parts:
|
| 876 |
# 转义引号和反斜杠
|
| 877 |
escaped = part.replace('\\', '\\\\').replace('"', '\\"')
|
| 878 |
escaped_parts.append(f'"{escaped}"')
|
| 879 |
+
|
| 880 |
return '[' + ', '.join(escaped_parts) + ']'
|
| 881 |
+
|
| 882 |
except Exception as e:
|
| 883 |
print(f"警告: 解析 exec 格式失败: {e}")
|
| 884 |
print(f"原始命令: {command}")
|
| 885 |
# 如果解析失败,转换为 shell 格式
|
| 886 |
return f'{self.preload_prefix} {command}'
|
| 887 |
+
|
| 888 |
def modify_dockerfile_content(self, content: str) -> Tuple[str, List[str]]:
|
| 889 |
"""
|
| 890 |
修改 Dockerfile 内容
|
|
|
|
| 893 |
lines = content.splitlines()
|
| 894 |
modified_lines = []
|
| 895 |
changes = []
|
| 896 |
+
|
| 897 |
for i, line in enumerate(lines, 1):
|
| 898 |
instruction, command, format_type = self.parse_dockerfile_line(line)
|
| 899 |
+
|
| 900 |
if instruction in ['CMD', 'ENTRYPOINT'] and command:
|
| 901 |
if format_type == "shell":
|
| 902 |
new_command = self.modify_shell_format(command)
|
|
|
|
| 906 |
new_line = f'{instruction} {new_command}'
|
| 907 |
else:
|
| 908 |
new_line = line
|
| 909 |
+
|
| 910 |
changes.append(f"第 {i} 行: {instruction} 指令已修改")
|
| 911 |
changes.append(f" 原始: {line}")
|
| 912 |
changes.append(f" 修改: {new_line}")
|
|
|
|
| 922 |
if last_user is None:
|
| 923 |
modified_lines.insert(-1, f"COPY {self.source_so_path}" + f" {self.tatget_so_path}")
|
| 924 |
else:
|
| 925 |
+
modified_lines.insert(-1,
|
| 926 |
+
f"COPY --chown={last_user}:{last_user} {self.source_so_path} {self.tatget_so_path}")
|
| 927 |
modified_lines.insert(-1, f"RUN chown -R {last_user}:{last_user} {self.tatget_so_dir}")
|
| 928 |
|
| 929 |
return '\n'.join(modified_lines), changes
|