Files
warmbly/internal/repository/pg_fleet_settings.go
T

95 lines
3.0 KiB
Go

package repository
import (
"context"
"encoding/json"
"errors"
"time"
"github.com/jackc/pgx/v5"
"github.com/warmbly/warmbly/internal/infrastructure/db"
"github.com/warmbly/warmbly/internal/models"
)
// FleetSettingsRepository holds the two instance-wide values the pull-based
// fleet needs: what version nodes should run, and the hash of the token they
// join with. Both live in admin_settings, so they are shared across backend
// replicas and survive a restart.
type FleetSettingsRepository interface {
GetRelease(ctx context.Context) (*models.FleetReleaseState, error)
SetRelease(ctx context.Context, state *models.FleetReleaseState) error
// GetJoinToken returns "" when no token has been issued yet, which callers
// must treat as "nothing may join" rather than "anything may". A nil
// expiry is a token issued before tokens carried one.
GetJoinToken(ctx context.Context) (hash string, expiresAt *time.Time, err error)
SetJoinToken(ctx context.Context, hash string, expiresAt time.Time) error
}
type fleetSettingsRepository struct {
db *db.DB
}
func NewFleetSettingsRepository(d *db.DB) FleetSettingsRepository {
return &fleetSettingsRepository{db: d}
}
func (r *fleetSettingsRepository) getRaw(ctx context.Context, key string) ([]byte, error) {
var raw []byte
err := r.db.QueryRow(ctx, `SELECT value FROM admin_settings WHERE key = $1`, key).Scan(&raw)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return raw, err
}
func (r *fleetSettingsRepository) setRaw(ctx context.Context, key string, v any) error {
raw, err := json.Marshal(v)
if err != nil {
return err
}
_, err = r.db.Exec(ctx, `
INSERT INTO admin_settings (key, value, updated_at)
VALUES ($1, $2, now())
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = now()
`, key, raw)
return err
}
func (r *fleetSettingsRepository) GetRelease(ctx context.Context) (*models.FleetReleaseState, error) {
raw, err := r.getRaw(ctx, models.FleetSettingsKeyRelease)
if err != nil || raw == nil {
return nil, err
}
var st models.FleetReleaseState
if err := json.Unmarshal(raw, &st); err != nil {
return nil, err
}
return &st, nil
}
func (r *fleetSettingsRepository) SetRelease(ctx context.Context, state *models.FleetReleaseState) error {
return r.setRaw(ctx, models.FleetSettingsKeyRelease, state)
}
type joinTokenValue struct {
Hash string `json:"hash"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
}
func (r *fleetSettingsRepository) GetJoinToken(ctx context.Context) (string, *time.Time, error) {
raw, err := r.getRaw(ctx, models.FleetSettingsKeyJoinToken)
if err != nil || raw == nil {
return "", nil, err
}
var v joinTokenValue
if err := json.Unmarshal(raw, &v); err != nil {
return "", nil, err
}
return v.Hash, v.ExpiresAt, nil
}
func (r *fleetSettingsRepository) SetJoinToken(ctx context.Context, hash string, expiresAt time.Time) error {
return r.setRaw(ctx, models.FleetSettingsKeyJoinToken, joinTokenValue{Hash: hash, ExpiresAt: &expiresAt})
}