39e1e36510
Build / build (push) Successful in 10m42s
Fourth trigger kind alongside registry/git/manual. Recurring time-interval fires driven by a new internal/scheduler tick loop (default 30s, clamped to 5m). Goes through the same webhook.Handler.FanOutForTrigger seam as inbound HTTP webhooks, so per-binding concurrency, outcome accounting, and config-merge semantics are identical. Schema: triggers.last_fired_at TEXT column (additive ALTER for existing DBs). Scheduler persists last_fired_at BEFORE dispatch so a panicking Match cannot wedge a tight loop; failed deploys wait one full interval before retry — correct trade-off for a periodic refresh trigger. Frontend: TriggerKindForm + /triggers/new + /triggers/[id] gain the schedule kind (4-col card grid, preset chips Hourly/Daily/Weekly, custom interval input matched to Go time.ParseDuration syntax, optional pinned reference). /triggers/[id] surfaces "last fired" on schedule rows. EN+RU i18n in parity. Review fixes from go-reviewer / security-reviewer / typescript-reviewer: - Scheduler Start/Stop wrapped in sync.Once (no goroutine leak / double- cancel panic on shutdown re-entry). - shouldFire rejects sub-MinInterval as defense-in-depth against hand-inserted rows that bypassed Validate. - fire() asserts trigger Kind=="schedule" before dispatching. - Aligned isValidInterval regex across all three frontend sites; reject the unsupported "d" unit (Go time.ParseDuration doesn't accept it). - formatLastFired falls back to lastFiredNever on malformed timestamps rather than leaking raw bytes into the UI. - main.go scheduler closure logs per-fire deployed/errored counts.
80 lines
3.0 KiB
Go
80 lines
3.0 KiB
Go
// Package plugin defines the Source and Trigger contracts that decouple
|
|
// Tinyforge's deployer pipeline from any single deployable shape (image,
|
|
// compose, static, ...) or any single redeploy trigger (registry push,
|
|
// git push, manual, ...).
|
|
//
|
|
// A Workload is the unifying user-facing entity. It carries an opaque
|
|
// SourceConfig (interpreted by the matching Source) and an opaque
|
|
// TriggerConfig (interpreted by the matching Trigger). Both kinds are
|
|
// strings; lookup happens through the registries below.
|
|
//
|
|
// New deployable shapes or trigger types are added by:
|
|
// 1. Implementing Source or Trigger in a sub-package.
|
|
// 2. Calling Register (Source/Trigger) from that package's init().
|
|
// 3. Blank-importing the sub-package from cmd/ to pull the registration in.
|
|
//
|
|
// No code in this package or in the deployer/api layers needs to change
|
|
// when a new kind appears — the registry is the only seam.
|
|
package plugin
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/alexei/tinyforge/internal/dns"
|
|
"github.com/alexei/tinyforge/internal/docker"
|
|
"github.com/alexei/tinyforge/internal/events"
|
|
"github.com/alexei/tinyforge/internal/health"
|
|
"github.com/alexei/tinyforge/internal/notify"
|
|
"github.com/alexei/tinyforge/internal/proxy"
|
|
"github.com/alexei/tinyforge/internal/store"
|
|
)
|
|
|
|
// Deps is the bundle of services every Source or Trigger may need. Passed
|
|
// per-call so plugin implementations stay stateless and testable.
|
|
type Deps struct {
|
|
Store *store.Store
|
|
Docker *docker.Client
|
|
Proxy proxy.Provider
|
|
DNS dns.Provider // nil when wildcard DNS is active
|
|
Health *health.Checker
|
|
Notifier *notify.Notifier
|
|
Events EventPublisher
|
|
EncKey [32]byte // pass-through to crypto.Encrypt/Decrypt for config secrets
|
|
}
|
|
|
|
// EventPublisher matches the deployer's existing event-bus surface. Kept as
|
|
// a local interface so plugin/ does not pull events transitively into every
|
|
// caller.
|
|
type EventPublisher interface {
|
|
Publish(evt events.Event)
|
|
}
|
|
|
|
// Workload is the value-shape every plugin consumes. It is constructed by
|
|
// the store layer from the workloads row plus its decoded JSON blobs; the
|
|
// physical schema can evolve independently of this struct.
|
|
type Workload struct {
|
|
ID string
|
|
Name string
|
|
GroupID string // formerly app_id; "" = ungrouped
|
|
ParentWorkloadID string // for stage chains; "" = root
|
|
|
|
SourceKind string // "image" | "compose" | "static" | ...
|
|
SourceConfig json.RawMessage // shape determined by SourceKind
|
|
|
|
TriggerKind string // "registry" | "git" | "manual" | "schedule" | ...
|
|
TriggerConfig json.RawMessage // shape determined by TriggerKind
|
|
|
|
PublicFaces []PublicFace // zero or more public routes
|
|
|
|
// Notification + webhook security live on the Workload itself rather
|
|
// than on per-kind tables so the rules are consistent across shapes.
|
|
NotificationURL string
|
|
NotificationSecret string
|
|
WebhookSecret string
|
|
WebhookSigningSecret string
|
|
WebhookRequireSignature bool
|
|
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|