feat: master/agent phase 2 — jobs and manual update (#7) #20

Merged
shcizo merged 29 commits from feat/master-agent-jobs-7 into main 2026-10-12 17:58:54 +00:00
3 changed files with 30 additions and 5 deletions
Showing only changes of commit c9c4e0cadb - Show all commits
+1 -1
View File
@@ -76,7 +76,7 @@ func run() error {
} }
auth := webauth.New(provider, st, cfg.AllowedGroup, strings.HasPrefix(cfg.PublicURL, "https://"), logger) auth := webauth.New(provider, st, cfg.AllowedGroup, strings.HasPrefix(cfg.PublicURL, "https://"), logger)
ui, err := web.New(st, agentHub, broker, web.Config{ ui, err := web.New(st, agentHub, nil, broker, web.Config{ // jobs is nil: wired in W3-4
AgentURL: cfg.AgentURL, MasterKey: fingerprint, AgentImage: cfg.AgentImage, AgentURL: cfg.AgentURL, MasterKey: fingerprint, AgentImage: cfg.AgentImage,
}, logger) }, logger)
if err != nil { if err != nil {
+13 -2
View File
@@ -28,6 +28,9 @@ var agentNamePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,62}$`)
// Store is the storage the UI reads and writes. *store.Store satisfies it. // Store is the storage the UI reads and writes. *store.Store satisfies it.
type Store interface { type Store interface {
ListServices(ctx context.Context) ([]model.Service, error) ListServices(ctx context.Context) ([]model.Service, error)
GetService(ctx context.Context, id int64) (model.Service, error)
GetJob(ctx context.Context, id int64) (model.Job, error)
ListJobs(ctx context.Context, f model.JobFilter) ([]model.Job, error)
ListAgents(ctx context.Context) ([]model.Agent, error) ListAgents(ctx context.Context) ([]model.Agent, error)
CreateAgent(ctx context.Context, name, tokenHash string) (model.Agent, error) CreateAgent(ctx context.Context, name, tokenHash string) (model.Agent, error)
RevokeAgent(ctx context.Context, id int64, at time.Time) error RevokeAgent(ctx context.Context, id int64, at time.Time) error
@@ -39,6 +42,13 @@ type Hub interface {
Disconnect(agentID int64) Disconnect(agentID int64)
} }
// Jobs starts updates and explains why a service cannot be updated.
// *jobs.Service satisfies it.
type Jobs interface {
Updatable(svc model.Service) error
Trigger(ctx context.Context, serviceID int64, requestedBy string) (model.Job, error)
}
// Broker delivers change signals to open streams and accepts signals from // Broker delivers change signals to open streams and accepts signals from
// UI actions. *events.Broker satisfies it. // UI actions. *events.Broker satisfies it.
type Broker interface { type Broker interface {
@@ -57,6 +67,7 @@ type Config struct {
type Server struct { type Server struct {
store Store store Store
hub Hub hub Hub
jobs Jobs
broker Broker broker Broker
cfg Config cfg Config
log *slog.Logger log *slog.Logger
@@ -66,7 +77,7 @@ type Server struct {
// New parses the templates. Each page is its own template set so every page // New parses the templates. Each page is its own template set so every page
// can define its own "content" block. // can define its own "content" block.
func New(store Store, hub Hub, broker Broker, cfg Config, log *slog.Logger) (*Server, error) { func New(store Store, hub Hub, jobs Jobs, broker Broker, cfg Config, log *slog.Logger) (*Server, error) {
funcs := template.FuncMap{"shortRef": shortRef} funcs := template.FuncMap{"shortRef": shortRef}
pages := map[string]*template.Template{} pages := map[string]*template.Template{}
for _, page := range []string{"overview.html", "agents.html", "agent_created.html"} { for _, page := range []string{"overview.html", "agents.html", "agent_created.html"} {
@@ -76,7 +87,7 @@ func New(store Store, hub Hub, broker Broker, cfg Config, log *slog.Logger) (*Se
} }
pages[page] = t pages[page] = t
} }
return &Server{store: store, hub: hub, broker: broker, cfg: cfg, log: log, now: time.Now, pages: pages}, nil return &Server{store: store, hub: hub, jobs: jobs, broker: broker, cfg: cfg, log: log, now: time.Now, pages: pages}, nil
} }
// Register adds the UI routes to mux. protect wraps every route except // Register adds the UI routes to mux. protect wraps every route except
+16 -2
View File
@@ -34,7 +34,14 @@ type fakeStore struct {
} }
func (f *fakeStore) ListServices(context.Context) ([]model.Service, error) { return f.services, nil } func (f *fakeStore) ListServices(context.Context) ([]model.Service, error) { return f.services, nil }
func (f *fakeStore) ListAgents(context.Context) ([]model.Agent, error) { return f.agents, nil } func (f *fakeStore) GetService(context.Context, int64) (model.Service, error) {
return model.Service{}, model.ErrNotFound
}
func (f *fakeStore) GetJob(context.Context, int64) (model.Job, error) {
return model.Job{}, model.ErrNotFound
}
func (f *fakeStore) ListJobs(context.Context, model.JobFilter) ([]model.Job, error) { return nil, nil }
func (f *fakeStore) ListAgents(context.Context) ([]model.Agent, error) { return f.agents, nil }
func (f *fakeStore) CreateAgent(_ context.Context, name, hash string) (model.Agent, error) { func (f *fakeStore) CreateAgent(_ context.Context, name, hash string) (model.Agent, error) {
f.mu.Lock() f.mu.Lock()
@@ -65,6 +72,13 @@ func (f *fakeStore) revoked() int64 {
return f.revokedID return f.revokedID
} }
type fakeJobs struct{}
func (fakeJobs) Updatable(model.Service) error { return nil }
func (fakeJobs) Trigger(context.Context, int64, string) (model.Job, error) {
return model.Job{}, model.ErrNotFound
}
type fakeHub struct { type fakeHub struct {
mu sync.Mutex mu sync.Mutex
online map[int64]bool online map[int64]bool
@@ -110,7 +124,7 @@ func newTestServer(t *testing.T) (*httptest.Server, *fakeStore, *fakeHub, *event
} }
hub := &fakeHub{online: map[int64]bool{2: true}} hub := &fakeHub{online: map[int64]bool{2: true}}
broker := events.NewBroker() broker := events.NewBroker()
s, err := New(store, hub, broker, Config{ s, err := New(store, hub, fakeJobs{}, broker, Config{
AgentURL: "wss://10.0.0.5:8090/agent", MasterKey: "sha256:abc", AgentImage: "gitea.shcizo.se/shcizo/package-updater:latest", AgentURL: "wss://10.0.0.5:8090/agent", MasterKey: "sha256:abc", AgentImage: "gitea.shcizo.se/shcizo/package-updater:latest",
}, slog.New(slog.NewTextHandler(io.Discard, nil))) }, slog.New(slog.NewTextHandler(io.Discard, nil)))
require.NoError(t, err) require.NoError(t, err)