Skip to content

[Refactor] Phase 3: Create Port Topology Domain Model #298

Description

@josecelano

Epic: #287 - Docker Compose Topology Domain Model Refactoring
Related Plan: docs/refactors/plans/docker-compose-topology-domain-model.md

Overview

Move port exposure logic from Tera templates to the domain layer, following the same pattern used for networks in Phase 1-2. This creates type-safe port definitions with descriptions that render as inline YAML comments for sysadmin self-documentation.

Background

This phase was originally part of the refactoring plan (see PORT-01 through PORT-11 rules in the plan) but was not included in the initial 5-PR strategy. The port exposure rules identified in the plan are currently scattered between Rust code and Tera templates, creating the same dual-source-of-truth problem that was solved for networks.

Problem Statement

Current State

Port exposure logic is split across multiple locations:

  1. Template conditionals - Complex {%- if %} blocks for TLS-based port exposure
  2. Rust filtering - http_tracker_ports_without_tls computed in context builder
  3. Hardcoded ports - Caddy (80, 443), Prometheus (9090), MySQL (none)

Problems

  1. Logic in templates - Port exposure decisions based on TLS configuration
  2. No cross-service validation - Cannot detect host port conflicts across services
  3. Missing documentation - Rendered YAML lacks context for what each port does
  4. Existing validation is tracker-only - TrackerConfig validates internal socket conflicts but not Docker Compose host port conflicts

Desired State

services:
  tracker:
    ports:
      # BitTorrent UDP announce (external)
      - "6969:6969/udp"
      # HTTP tracker announce (no TLS)
      - "7070:7070"
      # REST API for stats/whitelist (no TLS)
      - "1212:1212"

Port Rules from Original Plan

These rules (PORT-01 through PORT-11) will be moved to domain:

Rule ID Rule Description
PORT-01 Tracker needs ports section if UDP OR HTTP without TLS OR API without TLS
PORT-02 UDP ports always exposed (no TLS)
PORT-03 HTTP ports WITHOUT TLS exposed directly
PORT-04 HTTP ports WITH TLS NOT exposed (Caddy handles)
PORT-05 API exposed only when no TLS
PORT-06 API NOT exposed when TLS
PORT-07 Grafana 3000 exposed only without TLS
PORT-08 Grafana 3000 NOT exposed with TLS
PORT-09 Caddy always exposes 80, 443, 443/udp
PORT-10 Prometheus 9090 on localhost only
PORT-11 MySQL no exposed ports

Tasks

P3.1: Create Port Domain Types

  • Create Protocol enum (or reuse from domain/tracker)
  • Create PortBinding struct with description support
  • Add PortDefinition context type for template rendering
  • Add unit tests for port binding creation

P3.2: Extend ServiceTopology with Ports

  • Add ports: Vec<PortBinding> to ServiceTopology
  • Implement port derivation for each service type
  • Add unit tests for each service's port derivation

P3.3: Add Cross-Service Port Validation

  • Implement DockerComposeTopology::validate_port_uniqueness()
  • Create TopologyError::PortConflict variant
  • Add tests for conflict detection scenarios

P3.4: Update Template and Context

  • Create PortDefinition with binding() and description() for template
  • Update DockerComposeContext to use derived ports
  • Simplify docker-compose.yml.tera port sections
  • Remove conditional port logic from template

Acceptance Criteria

  • All PORT-* rules from refactoring plan are implemented in domain
  • Port descriptions render as YAML comments in output
  • Cross-service port conflicts are detected and reported with actionable error
  • Template has no conditional port logic (just loops)
  • No duplication with TrackerConfig validation (different scopes)
  • All existing E2E tests pass
  • Pre-commit checks pass

References

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions