mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-09-04 13:36:35 +02:00
"ctrld status" reported the service manager's view and nothing else, so it printed "Service is running" and exited 0 for the incident's process: alive, registered as started, stuck in API preflight, with no control socket, no DNS listener and no policy applied. The one command an operator reaches for first confirmed the service was fine while the host had no working DNS. Probe the control server's /started endpoint before reporting success. That endpoint only answers once the onStarted hooks have completed, which is after the listeners are up, so a successful probe means the process is serving rather than merely alive. A service that is registered as running but cannot confirm startup is now reported as such, with a pointer to the log, and exits 3 - distinct from stopped (1) and unknown (2), because it needs a different response. A probe blocked by permissions is not evidence of a broken service: an unprivileged caller still gets "Service is running", with a note that startup was not verified. The probe is bounded by a short timeout so status stays fast. Document the exit codes in the command's help, and cover the probe (ready, not finished starting, no socket, timed out) and the classification, including that an unreadable socket is not reported as a failure. The not-ready verdict is only reported when the probe could have found the daemon's socket. socketDir() is caller-relative on unix - the system directory when writable, the caller's home otherwise - so an unprivileged "ctrld status" looks somewhere the root-owned daemon never listened and gets ENOENT, which is "wrong path", not "not ready". Since only darwin has an elevation PreRun and the root-level alias has none, that is the normal invocation; reporting exit 3 there would have told a monitoring check to restart healthy daemons. Such a caller now gets the service manager's view with startup reported as unverified. Windows and mobile resolve the same directory for every caller, so the verdict stays fully available on the platform the hung start was seen on. A successful probe is still conclusive whoever ran it.
104 lines
3.1 KiB
Go
104 lines
3.1 KiB
Go
package cli
|
|
|
|
import "testing"
|
|
|
|
// TestServiceBinaryFromImagePath covers the ImagePath shapes Windows stores. Getting this
|
|
// wrong makes readinessVerifiable compare the wrong directories, and "ctrld status" would
|
|
// then report a healthy service as not-ready - the false positive the readiness exit code
|
|
// exists to avoid.
|
|
func TestServiceBinaryFromImagePath(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
imagePath string
|
|
want string
|
|
}{
|
|
{
|
|
// The installed form: quoted because the directory contains a space, with the
|
|
// service arguments following it.
|
|
name: "quoted path with arguments",
|
|
imagePath: `"C:\Program Files\Control D\ctrld.exe" run --config "C:\ProgramData\Control D\ctrld.toml"`,
|
|
want: `C:\Program Files\Control D\ctrld.exe`,
|
|
},
|
|
{
|
|
name: "quoted path without arguments",
|
|
imagePath: `"C:\Program Files\Control D\ctrld.exe"`,
|
|
want: `C:\Program Files\Control D\ctrld.exe`,
|
|
},
|
|
{
|
|
name: "unquoted path with arguments",
|
|
imagePath: `C:\ctrld\ctrld.exe run --cd abc123`,
|
|
want: `C:\ctrld\ctrld.exe`,
|
|
},
|
|
{
|
|
name: "unquoted path alone",
|
|
imagePath: `C:\ctrld\ctrld.exe`,
|
|
want: `C:\ctrld\ctrld.exe`,
|
|
},
|
|
{
|
|
name: "surrounding whitespace",
|
|
imagePath: ` "C:\ctrld\ctrld.exe" run `,
|
|
want: `C:\ctrld\ctrld.exe`,
|
|
},
|
|
{
|
|
// Unterminated quote: take what is there rather than returning nothing, since
|
|
// "" means "cannot tell" and would silently disable the check.
|
|
name: "unterminated quote",
|
|
imagePath: `"C:\ctrld\ctrld.exe run`,
|
|
want: `C:\ctrld\ctrld.exe run`,
|
|
},
|
|
{
|
|
name: "empty",
|
|
imagePath: "",
|
|
want: "",
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := serviceBinaryFromImagePath(tc.imagePath); got != tc.want {
|
|
t.Errorf("serviceBinaryFromImagePath(%q) = %q, want %q", tc.imagePath, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSameExecutableDir pins the comparison itself: Windows paths are case-insensitive, and
|
|
// an empty side means "cannot tell", which must never read as a match.
|
|
func TestSameExecutableDir(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
a string
|
|
b string
|
|
want bool
|
|
}{
|
|
{
|
|
name: "same directory",
|
|
a: `C:\Program Files\Control D\ctrld.exe`,
|
|
b: `C:\Program Files\Control D\ctrld.exe`,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "same directory different case",
|
|
a: `C:\Program Files\Control D\ctrld.exe`,
|
|
b: `c:\program files\control d\ctrld.exe`,
|
|
want: true,
|
|
},
|
|
{
|
|
// The case the check exists for: a copy run from a download directory
|
|
// resolves a different control socket than the installed service.
|
|
name: "different directory",
|
|
a: `C:\Program Files\Control D\ctrld.exe`,
|
|
b: `C:\Users\admin\Downloads\ctrld.exe`,
|
|
want: false,
|
|
},
|
|
{name: "unknown installed path", a: "", b: `C:\ctrld\ctrld.exe`, want: false},
|
|
{name: "unknown self path", a: `C:\ctrld\ctrld.exe`, b: "", want: false},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := sameExecutableDir(tc.a, tc.b); got != tc.want {
|
|
t.Errorf("sameExecutableDir(%q, %q) = %v, want %v", tc.a, tc.b, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|