package emitter import ( "fmt" "strings" "git.cloudinit.dev/coreci/orca/internal/jobspec" ) // SocketEmitter renders the systemd directives that implement the // R-007 socket-plumbing contract: workloads bind to // /run/orca/alloc-/port-.sock unless overridden via // service.bind = "127.0.0.1" (the only documented opt-in). // // The systemd side of the contract uses two directives: // // - RuntimeDirectory=orca/alloc- — systemd creates // /run/orca/alloc-/ owned by the service user (orca:orca) // with mode 0750. The directory is removed when the unit stops // (RuntimeDirectory= semantics). P08 emits one RuntimeDirectory= // line per port so each port's socket directory is created; the // alloc-id placeholder is spec.Name (the real alloc-id is assigned // by the scheduler at submit time — see allocIDFor). // // - ExecStartPre= — only when service.bind is "127.0.0.1" (the TCP // opt-in). In that case the workload binds a TCP port directly // (no socket), and the ExecStartPre is a placeholder that records // the bind (the actual bind happens in the process; the directive // is a no-op marker so operators can see the bind mode in the unit // file). When service.bind is empty (the default), the workload // binds the socket and no ExecStartPre is emitted for sockets. // // The socket path format is /run/orca/alloc-/port-.sock // where alloc-id is a PLACEHOLDER (spec.Name) — the real alloc-id is // assigned at submit time by the scheduler. The placeholder is // documented in the rendered unit via a comment so operators reading // the unit file understand the substitution. // // P08 is a PLAN/plumbing layer — the actual socket activation (socket // unit files, systemd socket-activation passing the pre-bound socket // fd to the process) lands in v0.10. P08 just renders the // RuntimeDirectory= lines and the optional TCP-bind ExecStartPre so // the directory exists at runtime. type SocketEmitter struct{} // runtimeDirectoryRoot is the systemd RuntimeDirectory path root. // systemd joins this with the RuntimeDirectory= value to create // /run/orca/alloc-. The leading slash is implicit in systemd // (RuntimeDirectory= is relative to /run). const runtimeDirectoryRoot = "orca" // SocketPath returns the R-007 socket path for a port on the given // alloc-id. The alloc-id is the placeholder spec.Name when the real // alloc-id is not yet known (the scheduler assigns the real alloc-id // at submit time). func SocketPath(allocID, portName string) string { return fmt.Sprintf("/run/orca/alloc-%s/port-%s.sock", allocID, portName) } // RenderSocketLines renders the systemd directives that implement // the R-007 socket plumbing for the given spec. The lines are returned // WITHOUT a trailing newline so the caller (the systemd emitter) can // append them to the [Service] block with consistent formatting. // // The returned lines are: // // - one RuntimeDirectory= line per port (so each port's socket // directory is created by systemd at unit start). // - a comment documenting the alloc-id placeholder. // - when service.bind is "127.0.0.1", an ExecStartPre= marker that // records the TCP opt-in (the actual bind is in the process). // // Returns an empty slice when the spec has no ports (no socket // plumbing needed — e.g. a Job or a port-less DaemonSet). func (SocketEmitter) RenderSocketLines(spec *jobspec.WorkloadSpec) []string { if spec == nil || len(spec.Ports) == 0 { return nil } allocID := allocIDForSocket(spec) var lines []string // One RuntimeDirectory= per port. systemd dedupes identical // values, but we emit one per port so the unit file is // self-documenting (each port maps to a directory entry). for _, p := range spec.Ports { lines = append(lines, fmt.Sprintf("RuntimeDirectory=%s/alloc-%s", runtimeDirectoryRoot, allocID)) // Document the socket path this directory serves. systemd // ignores comment lines (lines starting with '#'). lines = append(lines, fmt.Sprintf("# socket: %s", SocketPath(allocID, p.Name))) } // TCP opt-in: when service.bind is 127.0.0.1, the workload binds // a TCP port directly instead of the socket. We emit an // ExecStartPre marker so the bind mode is visible in the unit // file. The actual bind is in the process; the marker is a // no-op (echo to journald). if spec.Service != nil && strings.TrimSpace(spec.Service.Bind) != "" { if isTCPOptIn(spec.Service.Bind) { for _, p := range spec.Ports { lines = append(lines, fmt.Sprintf("ExecStartPre=/bin/echo orca: bind %s port %s (tcp, R-007 opt-in)", spec.Service.Bind, p.Name)) } } } return lines } // allocIDForSocket returns the alloc-id placeholder for the spec. The // real alloc-id is assigned by the scheduler at submit time; P08 uses // spec.Name as a deterministic placeholder so the rendered unit is // stable across re-renders. This mirrors the Traefik emitter's // allocIDFor (which uses the node hostname for the Traefik // dynamic-config server URL); the systemd unit is per-alloc, so // spec.Name is the right placeholder here. func allocIDForSocket(spec *jobspec.WorkloadSpec) string { if spec == nil || strings.TrimSpace(spec.Name) == "" { return "" } return spec.Name } // isTCPOptIn returns true when the bind value is the documented // 127.0.0.1 TCP opt-in (R-007). Other valid IPs (::1, etc.) are also // TCP opt-ins (any non-empty bind opts out of the socket default); we // only emit the marker for 127.0.0.1 because that is the only // documented opt-in per the PRD — other IPs are accepted by the // schema validator but are operator-specific and we do not // second-guess them. func isTCPOptIn(bind string) bool { return strings.TrimSpace(bind) == "127.0.0.1" }