8ecf8929fb
The control envelope becomes live: the agent caches the hub's desired-state +
generation and re-fetches GET /hosts/{id}/desired-state only when the
generation advances. A new internal/desired Syncer maps the wire shape into a
reconcile.CachingProvider feeding the engine; benign deltas reconcile, an
explicit guest decommission is gated pending_signature (exec is 10B). Adds the
DesiredStateResponse/WireDesiredState wire types + Client.FetchDesiredState +
the loop EnvelopeObserver seam. Cross-repo golden (envelope + desired-state)
byte-identical with the hub.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
63 lines
1.7 KiB
Go
63 lines
1.7 KiB
Go
package hub
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-agent/internal/proxmox"
|
|
)
|
|
|
|
func quietLogger() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) }
|
|
|
|
// roundTripFunc is a mock http.RoundTripper.
|
|
type roundTripFunc func(*http.Request) (*http.Response, error)
|
|
|
|
func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
|
|
|
|
// testClient builds a hub Client over a mock transport (no network).
|
|
func testClient(rt roundTripFunc) *Client {
|
|
return newClient("https://hub.example.test", "super-secret-bearer-key", "demo-host-01", &http.Client{Transport: rt}, quietLogger())
|
|
}
|
|
|
|
func httpResp(code int, body string) *http.Response {
|
|
return &http.Response{
|
|
StatusCode: code,
|
|
Body: io.NopCloser(strings.NewReader(body)),
|
|
Header: http.Header{"Content-Type": []string{"application/json"}},
|
|
}
|
|
}
|
|
|
|
// fakePx is a fake proxmoxReader.
|
|
type fakePx struct {
|
|
node string
|
|
ns proxmox.NodeStatus
|
|
nsErr error
|
|
lxc []proxmox.Guest
|
|
lxcErr error
|
|
cfg map[int]proxmox.GuestConfig
|
|
cfgErr map[int]error
|
|
}
|
|
|
|
func (f *fakePx) Node() string { return f.node }
|
|
func (f *fakePx) NodeStatus(ctx context.Context) (proxmox.NodeStatus, error) {
|
|
return f.ns, f.nsErr
|
|
}
|
|
func (f *fakePx) ListLXC(ctx context.Context) ([]proxmox.Guest, error) { return f.lxc, f.lxcErr }
|
|
func (f *fakePx) GuestConfig(ctx context.Context, vmid int) (proxmox.GuestConfig, error) {
|
|
if e := f.cfgErr[vmid]; e != nil {
|
|
return proxmox.GuestConfig{}, e
|
|
}
|
|
return f.cfg[vmid], nil
|
|
}
|
|
|
|
// fakeProber is a fake CloudflaredProber.
|
|
type fakeProber struct {
|
|
status string
|
|
err error
|
|
}
|
|
|
|
func (p fakeProber) Status(ctx context.Context) (string, error) { return p.status, p.err }
|