1
0
forked from wrenn/wrenn

Add UUID-based template IDs and team-scoped template directory layout

Introduces internal/layout package for centralized path construction,
migrates templates from name-based TEXT primary keys to UUID PKs with
team-scoped directories (WRENN_DIR/images/teams/{team_id}/{template_id}).
The built-in minimal template uses sentinel zero UUIDs. Proto messages
carry team_id + template_id alongside deprecated template name field.
Team deletion now cleans up template files across all hosts.
This commit is contained in:
2026-03-29 00:30:10 +06:00
parent 03e96629c7
commit 75b28ed899
24 changed files with 1057 additions and 322 deletions

View File

@ -36,8 +36,9 @@ func NewAuditLogID() pgtype.UUID { return newUUID() }
func NewBuildID() pgtype.UUID { return newUUID() }
func NewAdminPermissionID() pgtype.UUID { return newUUID() }
func NewTemplateID() pgtype.UUID { return newUUID() }
// NewSnapshotName generates a snapshot name: "template-" + 8 hex chars.
// Templates use TEXT primary keys (not UUID), so this stays as a string.
func NewSnapshotName() string {
return "template-" + hex8()
}
@ -76,8 +77,8 @@ const (
PrefixAdminPermission = "perm-"
)
// uuidToBase36 encodes 16 UUID bytes as a 25-char base36 string (0-9a-z).
func uuidToBase36(b [16]byte) string {
// UUIDToBase36 encodes 16 UUID bytes as a 25-char base36 string (0-9a-z).
func UUIDToBase36(b [16]byte) string {
n := new(big.Int).SetBytes(b[:])
buf := make([]byte, base36IDLen)
mod := new(big.Int)
@ -110,7 +111,7 @@ func base36ToUUID(s string) ([16]byte, error) {
}
func formatUUID(prefix string, id pgtype.UUID) string {
return prefix + uuidToBase36(id.Bytes)
return prefix + UUIDToBase36(id.Bytes)
}
func FormatSandboxID(id pgtype.UUID) string { return formatUUID(PrefixSandbox, id) }
@ -151,6 +152,17 @@ func ParseBuildID(s string) (pgtype.UUID, error) { return parseUUID(PrefixBu
// (e.g. base templates, shared infrastructure).
var PlatformTeamID = pgtype.UUID{Bytes: [16]byte{}, Valid: true}
// MinimalTemplateID is the all-zeros UUID sentinel for the built-in "minimal"
// template. When both team_id and template_id are zero, the host agent uses
// the minimal rootfs at WRENN_DIR/images/minimal/.
var MinimalTemplateID = pgtype.UUID{Bytes: [16]byte{}, Valid: true}
// UUIDString converts a pgtype.UUID to a standard hyphenated UUID string
// (e.g., "6ba7b810-9dad-11d1-80b4-00c04fd430c8"). Used for RPC wire format.
func UUIDString(id pgtype.UUID) string {
return uuid.UUID(id.Bytes).String()
}
// --- Helpers ---
func hex8() string {

View File

@ -10,7 +10,7 @@ import (
func TestBase36RoundTrip(t *testing.T) {
for i := 0; i < 1000; i++ {
orig := uuid.New()
encoded := uuidToBase36(orig)
encoded := UUIDToBase36(orig)
if len(encoded) != base36IDLen {
t.Fatalf("expected %d chars, got %d: %s", base36IDLen, len(encoded), encoded)
@ -29,7 +29,7 @@ func TestBase36RoundTrip(t *testing.T) {
func TestBase36ZeroUUID(t *testing.T) {
var zero [16]byte
encoded := uuidToBase36(zero)
encoded := UUIDToBase36(zero)
if encoded != "0000000000000000000000000" {
t.Fatalf("zero UUID should encode to all zeros, got %s", encoded)
}
@ -87,7 +87,7 @@ func TestPlatformTeamIDFormats(t *testing.T) {
func TestMaxUUID(t *testing.T) {
max := [16]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
encoded := uuidToBase36(max)
encoded := UUIDToBase36(max)
if len(encoded) != base36IDLen {
t.Fatalf("max UUID encoding wrong length: %d", len(encoded))
}