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-.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-.service`; the v0.9 path uses // `orca-v1-.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/.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 }