import yaml from typing import Tuple, List def grade_task4(submitted_config: str) -> Tuple[float, str, List[str]]: """ Grade Task 4: Broken docker-compose.yml (4 bugs) Bug 1 (Syntax): Wrong indentation on volumes key Bug 2 (Semantic): Service references non-existent network name Bug 3 (Runtime): Port mapping "8080:80" should be "8080:3000" Bug 4 (Integration): depends_on references "postgres" but service is defined as "db" Returns: (reward, error_message, bugs_fixed_list) """ bugs_fixed = [] total_bugs = 4 error_messages = [] # Bug 1: Check if YAML parses (syntax layer) try: config = yaml.safe_load(submitted_config) if not isinstance(config, dict): error_messages.append("docker-compose config is not a valid mapping") return 0.05, "; ".join(error_messages), bugs_fixed bugs_fixed.append("syntax_valid_yaml") except yaml.YAMLError as e: error_messages.append(f"YAML parse error: {str(e)}") return 0.05, "; ".join(error_messages), bugs_fixed services = config.get("services", {}) if not isinstance(services, dict): error_messages.append("'services' key is missing or not a mapping") reward = len(bugs_fixed) / total_bugs reward = max(0.01, min(0.90, reward)) return reward, "; ".join(error_messages), bugs_fixed defined_service_names = set(services.keys()) defined_networks = set(config.get("networks", {}).keys()) if isinstance(config.get("networks"), dict) else set() # Bug 4: Check depends_on references valid service names all_deps_valid = True for svc_name, svc_config in services.items(): if not isinstance(svc_config, dict): continue deps = svc_config.get("depends_on", []) if isinstance(deps, list): for dep in deps: if dep not in defined_service_names: all_deps_valid = False error_messages.append( f"Service '{svc_name}' depends_on '{dep}', " f"but no service named '{dep}' is defined. " f"Defined services: {sorted(defined_service_names)}" ) if all_deps_valid: bugs_fixed.append("depends_on_valid") # Bug 2: Check all referenced networks exist in top-level networks all_nets_valid = True for svc_name, svc_config in services.items(): if not isinstance(svc_config, dict): continue svc_networks = svc_config.get("networks", []) if isinstance(svc_networks, list): for net in svc_networks: if net not in defined_networks: all_nets_valid = False error_messages.append( f"Service '{svc_name}' references network '{net}', " f"but it's not defined in top-level networks. " f"Defined networks: {sorted(defined_networks)}" ) if all_nets_valid: bugs_fixed.append("networks_valid") # Bug 3: Check port mapping - web service should map to 3000 web_svc = services.get("web", {}) if isinstance(web_svc, dict): ports = web_svc.get("ports", []) port_correct = False for port_mapping in ports: port_str = str(port_mapping) # Check the container port (right side) is 3000 if ":3000" in port_str: port_correct = True break if port_correct: bugs_fixed.append("port_mapping_correct") else: error_messages.append( "Web service port mapping is incorrect. " "The application runs on port 3000, so the container port should be 3000." ) else: error_messages.append("Web service is missing or misconfigured") # Calculate reward reward = len(bugs_fixed) / total_bugs # 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