import re from typing import Tuple, List ALLOWED_BASE_IMAGES = [ "python:3.9-slim", "python:3.10-slim", "python:3.11-slim", "python:3.9", "python:3.10", "python:3.11", "python:3.9-alpine", "python:3.10-alpine", "python:3.11-alpine", "python:3.9-slim-bullseye", "python:3.9-slim-bookworm", "node:18", "node:20", "node:18-slim", "node:20-slim", "ubuntu:22.04", "ubuntu:20.04", "debian:bullseye-slim", ] def grade_task3(submitted_config: str) -> Tuple[float, str, List[str]]: """ Grade Task 3: Broken Dockerfile (3 bugs) Bug 1 (Syntax): RUN command with wrong escape character (^ instead of \\) Bug 2 (Semantic): Wrong base image tag (python:3.9-slm instead of python:3.9-slim) Bug 3 (Runtime): Missing EXPOSE directive Returns: (reward, error_message, bugs_fixed_list) """ bugs_fixed = [] total_bugs = 3 error_messages = [] lines = submitted_config.strip().split("\n") # Bug 2: Check base image is valid from_lines = [l for l in lines if l.strip().upper().startswith("FROM")] if from_lines: from_image = from_lines[0].strip().split()[1] if len(from_lines[0].strip().split()) > 1 else "" if from_image in ALLOWED_BASE_IMAGES: bugs_fixed.append("valid_base_image") else: error_messages.append( f"Base image '{from_image}' is not a recognized valid image. " f"Did you mean 'python:3.9-slim'?" ) else: error_messages.append("Missing FROM instruction in Dockerfile") # Bug 1: Check RUN syntax - no ^ for line continuation run_blocks = [] in_run = False current_run = [] for line in lines: stripped = line.strip() if stripped.upper().startswith("RUN "): in_run = True current_run = [line] elif in_run: if current_run[-1].rstrip().endswith("\\"): current_run.append(line) else: run_blocks.append("\n".join(current_run)) in_run = False current_run = [] if stripped.upper().startswith("RUN "): in_run = True current_run = [line] if not in_run and current_run: run_blocks.append("\n".join(current_run)) current_run = [] if current_run: run_blocks.append("\n".join(current_run)) has_caret = any("^" in line and not line.strip().startswith("#") for line in lines) # Check if multi-line RUN uses proper backslash has_proper_continuation = any( line.rstrip().endswith("\\") for line in lines if not line.strip().startswith("#") ) if not has_caret: bugs_fixed.append("run_syntax_correct") else: error_messages.append( "RUN command uses '^' for line continuation. " "Dockerfiles use '\\' for multi-line commands." ) # Bug 3: Check EXPOSE directive present expose_lines = [l for l in lines if l.strip().upper().startswith("EXPOSE")] if expose_lines: bugs_fixed.append("expose_present") else: error_messages.append( "Missing EXPOSE directive. The application port should be exposed." ) # Calculate reward reward = len(bugs_fixed) / total_bugs # Bonus for having a parseable Dockerfile structure has_from = any(l.strip().upper().startswith("FROM") for l in lines) has_cmd_or_entrypoint = any( l.strip().upper().startswith(("CMD", "ENTRYPOINT")) for l in lines ) # Clamp to strict (0,1) range for validator reward = max(0.01, min(0.90, reward)) error_msg = "; ".join(error_messages) if error_messages else "All checks passed!" return reward, error_msg, bugs_fixed