Files
LINUX-AETHERFORGE/internal/fleet/policy_snapshot.go
drjones 3678b199d0
Some checks failed
Test / test (push) Has been cancelled
Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
2026-07-04 09:31:23 +00:00

92 lines
2.2 KiB
Go

package fleet
import (
"crypto/rand"
"database/sql"
"encoding/hex"
"encoding/json"
"fmt"
"time"
)
// PolicySnapshot is a shareable frozen policy bundle.
type PolicySnapshot struct {
Token string `json:"token"`
PolicyJSON json.RawMessage `json:"policy_json"`
ExpiresAt string `json:"expires_at,omitempty"`
CreatedAt string `json:"created_at"`
}
// CreatePolicySnapshot stores a policy JSON blob and returns an opaque token.
func (s *Store) CreatePolicySnapshot(policy map[string]any, ttl time.Duration) (*PolicySnapshot, error) {
raw, err := json.Marshal(policy)
if err != nil {
return nil, fmt.Errorf("marshal policy: %w", err)
}
token, err := randomToken(16)
if err != nil {
return nil, err
}
now := time.Now().UTC()
expires := ""
if ttl > 0 {
expires = now.Add(ttl).Format(time.RFC3339)
}
_, err = s.db.Exec(`
INSERT INTO policy_snapshots (token, policy_json, expires_at, created_at)
VALUES (?, ?, ?, ?)
`, token, string(raw), nullIfEmpty(expires), now.Format(time.RFC3339))
if err != nil {
return nil, fmt.Errorf("insert policy snapshot: %w", err)
}
return &PolicySnapshot{
Token: token,
PolicyJSON: raw,
ExpiresAt: expires,
CreatedAt: now.Format(time.RFC3339),
}, nil
}
// GetPolicySnapshot loads a snapshot by token (public summon link).
func (s *Store) GetPolicySnapshot(token string) (*PolicySnapshot, error) {
row := s.db.QueryRow(`
SELECT token, policy_json, expires_at, created_at
FROM policy_snapshots WHERE token = ?
`, token)
var snap PolicySnapshot
var expires sql.NullString
var created string
if err := row.Scan(&snap.Token, &snap.PolicyJSON, &expires, &created); err != nil {
return nil, err
}
if expires.Valid {
snap.ExpiresAt = expires.String
t, err := time.Parse(time.RFC3339, expires.String)
if err == nil && time.Now().After(t) {
return nil, sql.ErrNoRows
}
}
snap.CreatedAt = created
return &snap, nil
}
func randomToken(n int) (string, error) {
b := make([]byte, n)
if _, err := rand.Read(b); err != nil {
return "", err
}
return hex.EncodeToString(b), nil
}
func nullIfEmpty(s string) sql.NullString {
if s == "" {
return sql.NullString{}
}
return sql.NullString{String: s, Valid: true}
}