60b0357eb6
P0c — Kind-specific schema validators + Layer 4 emitter interface.
Schemas (internal/spec/schema/schema.go, REQ-074):
- Validator interface with JobValidator, ServiceValidator, DaemonSetValidator.
JobValidator: count=1, no service block, optional schedule/timeout.
ServiceValidator: ports required, count>=1, restart+update+runtime required.
DaemonSetValidator: schedule mode required, no ports (D-175), no count.
ValidatorFor(kind) dispatcher. 96.2% coverage.
Emitter interface (internal/emitter/emitter.go, REQ-074, I-B-002):
- File{Path,Content,Mode}, Emitter interface { Render(spec,node) []File },
Registry keyed by kind:runtime, Register + Render lookup. 100% coverage.
Systemd stub (internal/emitter/systemd.go):
- SystemdEmitter for process runtime. Renders minimal [Service] unit at
/etc/systemd/system/orca-v1-alloc-<name>.service (orca-v1- prefix per
dual-write window REQ-090 — no overlap with v0.8 daemon's orca-<job>).
Flock test fix: TestFlock_concurrentBlocks rewritten to use non-blocking
tryFlockEx (LOCK_NB) instead of a leaked blocking goroutine. Eliminates
the temp-dir cleanup race.
20 packages pass, 20 bats pass, gofmt clean, verify-reqs 90 consistent.
---ci---
project: orca
phase: P0c
milestone: v0.9
status: execute
---/ci---
61 lines
2.4 KiB
Go
61 lines
2.4 KiB
Go
package emitter
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.cloudinit.dev/coreci/orca/internal/jobspec"
|
|
)
|
|
|
|
// SystemdEmitter is a stub Emitter implementation for the "process"
|
|
// runtime. It renders a minimal systemd unit file for the workload.
|
|
//
|
|
// This is a STUB — the full systemd emitter (with lifecycle hooks,
|
|
// sockets, EnvironmentFile, LoadCredential) lands in later phases:
|
|
//
|
|
// - P04: lifecycle hooks (ExecStop, ExecStartPre/Post, timeouts)
|
|
// - P08: socket plumbing (R-007)
|
|
// - v0.10-P03: secrets via EnvironmentFile= + LoadCredential=
|
|
//
|
|
// P0c ships only the minimal [Service]\nExecStart=... shape to prove
|
|
// the Emitter interface end-to-end. The unit name carries the
|
|
// `orca-v1-` prefix per the dual-write window (REQ-090) so the v0.9
|
|
// SSH-push path does not collide with the v0.8 daemon's
|
|
// `orca-<job>.service` units during the migration window.
|
|
type SystemdEmitter struct{}
|
|
|
|
// unitNamePrefix is the v0.9 SSH-push unit-name prefix. The v0.8
|
|
// daemon uses `orca-<job>.service`; the v0.9 path uses
|
|
// `orca-v1-<spec.Name>.service` so the two never overlap (REQ-090,
|
|
// I-C-006). The prefix is load-bearing — do not change it without
|
|
// updating the dual-write window contract.
|
|
const unitNamePrefix = "orca-v1-"
|
|
|
|
// Render renders a minimal systemd unit file for a process-runtime
|
|
// workload. The unit name is `/etc/systemd/system/<unitNamePrefix><spec.Name>.service`
|
|
// and the content is a minimal `[Service]` block with the runtime
|
|
// command as ExecStart. Mode is 0644 (the lead applier chmods after
|
|
// atomic rename).
|
|
//
|
|
// Returns an error if the spec is nil, the spec is missing its name,
|
|
// or the runtime command is empty (a workload with no command has
|
|
// nothing to ExecStart).
|
|
func (SystemdEmitter) Render(spec *jobspec.WorkloadSpec, node *Node) ([]File, error) {
|
|
if spec == nil {
|
|
return nil, errors.New("emitter/systemd: spec is nil")
|
|
}
|
|
if strings.TrimSpace(spec.Name) == "" {
|
|
return nil, errors.New("emitter/systemd: spec name is empty")
|
|
}
|
|
if spec.Runtime == nil {
|
|
return nil, errors.New("emitter/systemd: runtime block is nil")
|
|
}
|
|
if strings.TrimSpace(spec.Runtime.Command) == "" {
|
|
return nil, errors.New("emitter/systemd: runtime command is empty")
|
|
}
|
|
path := fmt.Sprintf("/etc/systemd/system/%s%s.service", unitNamePrefix, spec.Name)
|
|
content := fmt.Sprintf("[Service]\nExecStart=%s\n", spec.Runtime.Command)
|
|
return []File{{Path: path, Content: content, Mode: "0644"}}, nil
|
|
}
|