fix: finish progressive Target detection

This commit is contained in:
proxyctl coordinator
2026-07-17 21:25:43 +08:00
parent ad651402fd
commit 8348fa3ac8
17 changed files with 672 additions and 221 deletions
+32 -18
View File
@@ -348,15 +348,18 @@ func (a *Adapter) DiagnosticChecks(ctx context.Context, _ *profile.Profile) []ta
}
func (a *Adapter) Availability(ctx context.Context) target.Availability {
availability, _ := a.detectAvailability(ctx)
availability, _, _ := a.detectAvailability(ctx)
return availability
}
func (a *Adapter) detectAvailability(ctx context.Context) (target.Availability, error) {
// detectAvailability returns the settings observation that established
// availability so Detect can inspect that exact observation without issuing a
// second twelve-command gsettings probe.
func (a *Adapter) detectAvailability(ctx context.Context) (target.Availability, settings, error) {
output, err := a.run(ctx, "list-schemas")
if err != nil {
safeErr := redact(err)
return target.Availability{Evidence: fmt.Sprintf("GNOME Target requires a usable %s command: %v", a.configuration.CommandName, safeErr)}, safeErr
return target.Availability{Evidence: fmt.Sprintf("GNOME Target requires a usable %s command: %v", a.configuration.CommandName, safeErr)}, settings{}, safeErr
}
present := make(map[string]bool)
for _, schema := range strings.Fields(output) {
@@ -364,49 +367,60 @@ func (a *Adapter) detectAvailability(ctx context.Context) (target.Availability,
}
for _, schema := range requiredSchemas {
if !present[schema] {
return target.Availability{Evidence: "required GNOME settings schema is unavailable: " + schema}, nil
return target.Availability{Evidence: "required GNOME settings schema is unavailable: " + schema}, settings{}, nil
}
}
if _, err := a.readSettings(ctx); err != nil {
current, err := a.readSettings(ctx)
if err != nil {
safeErr := redact(err)
return target.Availability{Evidence: fmt.Sprintf("GNOME proxy schema is present but unusable for the desktop session: %v", safeErr)}, safeErr
return target.Availability{Evidence: fmt.Sprintf("GNOME proxy schema is present but unusable for the desktop session: %v", safeErr)}, settings{}, safeErr
}
return target.Availability{Available: true, Evidence: "gsettings command and GNOME proxy schemas are usable for the owning user"}, nil
return target.Availability{Available: true, Evidence: "gsettings command and GNOME proxy schemas are usable for the owning user"}, current, nil
}
func (a *Adapter) Detect(ctx context.Context, active *profile.Profile) target.Detection {
availability, err := a.detectAvailability(ctx)
return target.Detection{Availability: availability, Inspection: a.Inspect(ctx, active), Err: err}
availability, current, availabilityErr := a.detectAvailability(ctx)
if !availability.Available && availabilityErr == nil {
return target.Detection{Availability: availability}
}
inspection, inspectionErr := a.inspectSettings(active, current, availabilityErr)
return target.Detection{Availability: availability, Inspection: inspection, Err: errors.Join(availabilityErr, inspectionErr)}
}
func (a *Adapter) Inspect(ctx context.Context, active *profile.Profile) target.Inspection {
metadata, managed, metadataErr := a.readRecovery()
current, settingsErr := a.readSettings(ctx)
inspection, _ := a.inspectSettings(active, current, settingsErr)
return inspection
}
func (a *Adapter) inspectSettings(active *profile.Profile, current settings, settingsErr error) (target.Inspection, error) {
metadata, managed, metadataErr := a.readRecovery()
if managed || (metadataErr != nil && !errors.Is(metadataErr, fs.ErrNotExist)) {
if metadataErr != nil {
return target.Inspection{State: target.Drifted, Evidence: "managed GNOME recovery metadata cannot be read"}
return target.Inspection{State: target.Drifted, Evidence: "managed GNOME recovery metadata cannot be read"}, redact(metadataErr)
}
if settingsErr != nil {
return target.Inspection{State: target.Drifted, Evidence: "managed GNOME settings cannot be read: " + redact(settingsErr).Error()}
return target.Inspection{State: target.Drifted, Evidence: "managed GNOME settings cannot be read: " + redact(settingsErr).Error()}, redact(settingsErr)
}
if fingerprint(current) == metadata.LastFingerprint && active != nil {
desired, err := translate(*active)
if err == nil && fingerprint(desired) == metadata.LastFingerprint {
return target.Inspection{State: target.Enabled, Evidence: "managed GNOME settings match the Active Profile"}
return target.Inspection{State: target.Enabled, Evidence: "managed GNOME settings match the Active Profile"}, nil
}
}
return target.Inspection{State: target.Drifted, Evidence: "managed GNOME settings do not match the Active Profile"}
return target.Inspection{State: target.Drifted, Evidence: "managed GNOME settings do not match the Active Profile"}, nil
}
if settingsErr != nil {
return target.Inspection{State: target.Disabled, Evidence: "GNOME settings could not be inspected: " + redact(settingsErr).Error()}
safeErr := redact(settingsErr)
return target.Inspection{State: target.Disabled, Evidence: "GNOME settings could not be inspected: " + safeErr.Error()}, safeErr
}
switch current.Mode {
case "manual":
return target.Inspection{State: target.External, Evidence: "recognized external GNOME manual proxy mode is configured"}
return target.Inspection{State: target.External, Evidence: "recognized external GNOME manual proxy mode is configured"}, nil
case "auto":
return target.Inspection{State: target.External, Evidence: "recognized external GNOME automatic proxy mode is configured"}
return target.Inspection{State: target.External, Evidence: "recognized external GNOME automatic proxy mode is configured"}, nil
default:
return target.Inspection{State: target.Disabled, Evidence: "GNOME proxy mode is none; no recognized desktop proxy configuration detected"}
return target.Inspection{State: target.Disabled, Evidence: "GNOME proxy mode is none; no recognized desktop proxy configuration detected"}, nil
}
}
+19
View File
@@ -108,6 +108,25 @@ func active() *profile.Profile {
return &profile.Profile{Name: "office", Mode: profile.AdvancedMode, HTTPURL: "http://proxy.example:8080", HTTPSURL: "http://secure.example:8443", AllURL: "socks5://socks.example:1080", Bypass: []string{"localhost", ".example.com", "10.0.0.0/8", "2001:db8::1"}}
}
func TestDetectClassifiesMalformedRecoveryMetadataAsFailureWithoutRepeatingSettingsProbe(t *testing.T) {
runner := newRunner()
a := adapter(runner, &memoryState{data: []byte("{malformed")})
detection := a.Detect(context.Background(), active())
if detection.Err == nil || detection.Inspection.State != target.Drifted {
t.Fatalf("detection = %#v, want failed Drifted inspection", detection)
}
gets := 0
for _, call := range runner.calls {
if len(call.Args) > 0 && call.Args[0] == "get" {
gets++
}
}
if gets != 12 {
t.Fatalf("Detect issued %d gsettings get probes, want one 12-setting inspection", gets)
}
}
func TestAuthenticationPasswordSetFailureSuppressesRunnerErrorAndProfileArgument(t *testing.T) {
const profilePassword = "GNOME-PROFILE-PASSWORD-07"
r, state := newRunner(), &memoryState{}