mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-19 16:01:16 +00:00
2430095197
Remove DynamoDB-backed storage paths, add Postgres/HTTP repositories for mailbox state maps, wire the internal message-map API, and add provisioning runner/migration plumbing.
134 lines
4.3 KiB
Go
134 lines
4.3 KiB
Go
package provisioning
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/warmbly/warmbly/internal/infrastructure/cloudprovider"
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
"github.com/warmbly/warmbly/internal/repository"
|
|
)
|
|
|
|
// Runner is the background loop that actually drives provisioning jobs.
|
|
//
|
|
// Previously nothing called Service.Run, so a job created from the admin UI sat
|
|
// in "pending" forever. The Runner polls for in-flight jobs and drives each one
|
|
// to a terminal state (completed/failed). Run is idempotent and resumes from a
|
|
// job's current state, so a backend restart mid-provision is recoverable.
|
|
type Runner struct {
|
|
Jobs repository.ProvisioningJobRepository
|
|
Svc *Service
|
|
Interval time.Duration // default 15s
|
|
// DryRun is informational — it only affects the boot log line. The actual
|
|
// dry-run behaviour comes from the Service's provider/installer wiring.
|
|
DryRun bool
|
|
}
|
|
|
|
// Run blocks until ctx is cancelled, processing in-flight jobs on each tick.
|
|
func (r *Runner) Run(ctx context.Context) {
|
|
interval := r.Interval
|
|
if interval == 0 {
|
|
interval = 15 * time.Second
|
|
}
|
|
log.Info().
|
|
Bool("dry_run", r.DryRun).
|
|
Dur("interval", interval).
|
|
Msg("provisioning runner started")
|
|
|
|
tick := time.NewTicker(interval)
|
|
defer tick.Stop()
|
|
r.processOnce(ctx)
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-tick.C:
|
|
r.processOnce(ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *Runner) processOnce(ctx context.Context) {
|
|
if r.Svc == nil || r.Jobs == nil {
|
|
return
|
|
}
|
|
jobs, err := r.Jobs.ListInFlight(ctx)
|
|
if err != nil {
|
|
log.Warn().Err(err).Msg("provisioning runner: list in-flight failed")
|
|
return
|
|
}
|
|
for i := range jobs {
|
|
job := jobs[i]
|
|
// rolling_back is a transient state the Service sets just before it
|
|
// fails a job; skip it so we don't log a spurious "already terminal".
|
|
if job.State == models.ProvJobRollingBack {
|
|
continue
|
|
}
|
|
if err := r.Svc.Run(ctx, job.ID); err != nil {
|
|
log.Warn().Err(err).Str("job", job.ID.String()).Msg("provisioning job failed")
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Dry-run provider
|
|
// ---------------------------------------------------------------------------
|
|
|
|
var dryRunSeq atomic.Uint64
|
|
|
|
// DryRunProvider implements cloudprovider.Provider without touching any real
|
|
// cloud API. It returns plausible fake server/IP identifiers so the state
|
|
// machine can run end-to-end in local dev (and in any environment where
|
|
// PROVISIONING_DRY_RUN is on) without creating — or billing — real machines.
|
|
type DryRunProvider struct{}
|
|
|
|
func (DryRunProvider) Name() string { return "dry-run" }
|
|
|
|
func (DryRunProvider) Locations(context.Context) ([]cloudprovider.Location, error) {
|
|
return []cloudprovider.Location{}, nil
|
|
}
|
|
func (DryRunProvider) ServerTypes(context.Context) ([]cloudprovider.ServerType, error) {
|
|
return []cloudprovider.ServerType{}, nil
|
|
}
|
|
func (DryRunProvider) Images(context.Context) ([]cloudprovider.Image, error) {
|
|
return []cloudprovider.Image{}, nil
|
|
}
|
|
func (DryRunProvider) Verify(context.Context) error { return nil }
|
|
|
|
func (DryRunProvider) CreateServer(_ context.Context, req cloudprovider.CreateServerRequest) (*cloudprovider.Server, error) {
|
|
n := dryRunSeq.Add(1)
|
|
return &cloudprovider.Server{
|
|
ID: fmt.Sprintf("dryrun-%d", n),
|
|
Name: req.Name,
|
|
Status: "running",
|
|
PublicIPv4: dryRunIP(n),
|
|
}, nil
|
|
}
|
|
|
|
func (DryRunProvider) DeleteServer(context.Context, string) error { return nil }
|
|
|
|
func (DryRunProvider) CreatePrimaryIP(_ context.Context, req cloudprovider.CreatePrimaryIPRequest) (*cloudprovider.PrimaryIP, error) {
|
|
n := dryRunSeq.Add(1)
|
|
return &cloudprovider.PrimaryIP{
|
|
ID: fmt.Sprintf("dryrun-ip-%d", n),
|
|
Type: req.Type,
|
|
IP: dryRunIP(n),
|
|
}, nil
|
|
}
|
|
|
|
func (DryRunProvider) AssignPrimaryIP(context.Context, string, string) error { return nil }
|
|
func (DryRunProvider) UnassignPrimaryIP(context.Context, string) error { return nil }
|
|
func (DryRunProvider) DeletePrimaryIP(context.Context, string) error { return nil }
|
|
func (DryRunProvider) SetReverseDNS(context.Context, string, string) error { return nil }
|
|
|
|
// dryRunIP maps a sequence number into a deterministic 10.x.x.x address so the
|
|
// inet[] columns get a valid value.
|
|
func dryRunIP(n uint64) string {
|
|
return fmt.Sprintf("10.%d.%d.%d", (n>>16)&0xff, (n>>8)&0xff, (n&0xfe)+1)
|
|
}
|
|
|
|
var _ cloudprovider.Provider = DryRunProvider{}
|