Add initial Go server skeleton with HTTP handlers, middleware, job runner, and stubs
This commit is contained in:
42
internal/infra/exec/exec.go
Normal file
42
internal/infra/exec/exec.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package exec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RunOptions struct {
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
type CommandRunner interface {
|
||||
Run(ctx context.Context, cmd string, args []string, opts RunOptions) (string, string, error)
|
||||
}
|
||||
|
||||
type DefaultRunner struct{}
|
||||
|
||||
func (r *DefaultRunner) Run(ctx context.Context, cmd string, args []string, opts RunOptions) (string, string, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
execCtx := ctx
|
||||
if opts.Timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
execCtx, cancel = context.WithTimeout(ctx, opts.Timeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
eg, cctx := errgroup.WithContext(execCtx)
|
||||
|
||||
eg.Go(func() error {
|
||||
command := exec.CommandContext(cctx, cmd, args...)
|
||||
command.Stdout = &stdout
|
||||
command.Stderr = &stderr
|
||||
return command.Run()
|
||||
})
|
||||
if err := eg.Wait(); err != nil {
|
||||
return stdout.String(), stderr.String(), err
|
||||
}
|
||||
return stdout.String(), stderr.String(), nil
|
||||
}
|
||||
15
internal/infra/sqlite/db/db.go
Normal file
15
internal/infra/sqlite/db/db.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// Open returns a connected DB instance. Caller should close.
|
||||
func Open(ctx context.Context, dsn string) (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite", dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
44
internal/infra/sqlite/db/migrations.go
Normal file
44
internal/infra/sqlite/db/migrations.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// MigrateAndSeed performs a very small migration set and seeds an admin user
|
||||
func MigrateAndSeed(ctx context.Context, db *sql.DB) error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
stmts := []string{
|
||||
`CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, username TEXT NOT NULL UNIQUE, password_hash TEXT, role TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);`,
|
||||
`CREATE TABLE IF NOT EXISTS pools (name TEXT PRIMARY KEY, guid TEXT, health TEXT, capacity TEXT);`,
|
||||
`CREATE TABLE IF NOT EXISTS jobs (id TEXT PRIMARY KEY, type TEXT, status TEXT, progress INTEGER DEFAULT 0, owner TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME);`,
|
||||
}
|
||||
for _, s := range stmts {
|
||||
if _, err := tx.ExecContext(ctx, s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Seed a default admin user if not exists
|
||||
var count int
|
||||
if err := tx.QueryRowContext(ctx, `SELECT COUNT(1) FROM users WHERE username = 'admin'`).Scan(&count); err != nil {
|
||||
log.Printf("seed check failed: %v", err)
|
||||
}
|
||||
if count == 0 {
|
||||
// note: simple seeded password: admin (do not use in prod)
|
||||
pwHash, _ := bcrypt.GenerateFromPassword([]byte("admin"), bcrypt.DefaultCost)
|
||||
if _, err := tx.ExecContext(ctx, `INSERT INTO users (id, username, password_hash, role) VALUES (?, 'admin', ?, 'admin')`, "admin", string(pwHash)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
18
internal/infra/stubs/iscsi.go
Normal file
18
internal/infra/stubs/iscsi.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package stubs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
)
|
||||
|
||||
type ISCSIAdapter struct{}
|
||||
|
||||
func (i *ISCSIAdapter) CreateTarget(ctx context.Context, name string) error {
|
||||
log.Printf("iscsi: CreateTarget name=%s (stub)", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ISCSIAdapter) CreateLUN(ctx context.Context, target string, backstore string, lunID int) error {
|
||||
log.Printf("iscsi: CreateLUN target=%s backstore=%s lun=%d (stub)", target, backstore, lunID)
|
||||
return nil
|
||||
}
|
||||
18
internal/infra/stubs/minio.go
Normal file
18
internal/infra/stubs/minio.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package stubs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
)
|
||||
|
||||
type MinioAdapter struct{}
|
||||
|
||||
func (m *MinioAdapter) ListBuckets(ctx context.Context) ([]string, error) {
|
||||
log.Println("minio: ListBuckets (stub)")
|
||||
return []string{"buckets"}, nil
|
||||
}
|
||||
|
||||
func (m *MinioAdapter) CreateBucket(ctx context.Context, name string) error {
|
||||
log.Printf("minio: CreateBucket %s (stub)", name)
|
||||
return nil
|
||||
}
|
||||
18
internal/infra/stubs/nfs.go
Normal file
18
internal/infra/stubs/nfs.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package stubs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
)
|
||||
|
||||
type NFSAdapter struct{}
|
||||
|
||||
func (n *NFSAdapter) ListExports(ctx context.Context) ([]string, error) {
|
||||
log.Println("nfs: ListExports (stub)")
|
||||
return []string{"/export/data"}, nil
|
||||
}
|
||||
|
||||
func (n *NFSAdapter) CreateExport(ctx context.Context, path string) error {
|
||||
log.Printf("nfs: CreateExport path=%s (stub)", path)
|
||||
return nil
|
||||
}
|
||||
18
internal/infra/stubs/samba.go
Normal file
18
internal/infra/stubs/samba.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package stubs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
)
|
||||
|
||||
type SambaAdapter struct{}
|
||||
|
||||
func (s *SambaAdapter) ListShares(ctx context.Context) ([]string, error) {
|
||||
log.Println("samba: ListShares (stub)")
|
||||
return []string{"share1"}, nil
|
||||
}
|
||||
|
||||
func (s *SambaAdapter) CreateShare(ctx context.Context, name, path string) error {
|
||||
log.Printf("samba: CreateShare name=%s path=%s (stub)", name, path)
|
||||
return nil
|
||||
}
|
||||
18
internal/infra/stubs/zfs.go
Normal file
18
internal/infra/stubs/zfs.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package stubs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
)
|
||||
|
||||
type ZFSAdapter struct{}
|
||||
|
||||
func (z *ZFSAdapter) ListPools(ctx context.Context) ([]string, error) {
|
||||
log.Printf("zfs: ListPools (stub)")
|
||||
return []string{"tank"}, nil
|
||||
}
|
||||
|
||||
func (z *ZFSAdapter) CreatePool(ctx context.Context, name string, vdevs []string) error {
|
||||
log.Printf("zfs: CreatePool %s (stub) vdevs=%v", name, vdevs)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user