Add per-device routing long-tail: hosts, providers, routes, routing_rules
Storage + CRUD (migration 0006, model, id-keyed store, REST handlers) plus compiler rendering: each section is owned by a device and projected into that device's rendered config (hosts/providers/routes/routing_rules).
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"git.unkin.net/unkin/tomswallapi/internal/model"
|
||||
)
|
||||
|
||||
// ---- Hosts -----------------------------------------------------------------
|
||||
|
||||
func (s *Store) ListHosts(ctx context.Context) ([]model.Host, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT id, device, zone, interface, addresses, exclusions, dynamic FROM hosts ORDER BY id`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []model.Host
|
||||
for rows.Next() {
|
||||
h, err := scanHost(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, h)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) GetHost(ctx context.Context, id int64) (model.Host, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT id, device, zone, interface, addresses, exclusions, dynamic FROM hosts WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return model.Host{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return model.Host{}, ErrNotFound
|
||||
}
|
||||
return scanHost(rows)
|
||||
}
|
||||
|
||||
func scanHost(rows pgx.Rows) (model.Host, error) {
|
||||
var h model.Host
|
||||
var addrs, excl []byte
|
||||
if err := rows.Scan(&h.ID, &h.Device, &h.Zone, &h.Interface, &addrs, &excl, &h.Dynamic); err != nil {
|
||||
return h, err
|
||||
}
|
||||
if err := unmarshalStrings(addrs, &h.Addresses); err != nil {
|
||||
return h, err
|
||||
}
|
||||
return h, unmarshalStrings(excl, &h.Exclusions)
|
||||
}
|
||||
|
||||
func (s *Store) CreateHost(ctx context.Context, h model.Host) (int64, error) {
|
||||
addrs, _ := jsonb(h.Addresses)
|
||||
excl, _ := jsonb(h.Exclusions)
|
||||
var id int64
|
||||
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
||||
if err := tx.QueryRow(ctx, `
|
||||
INSERT INTO hosts (device, zone, interface, addresses, exclusions, dynamic)
|
||||
VALUES ($1, $2, $3, $4, $5, $6) RETURNING id`,
|
||||
h.Device, h.Zone, h.Interface, addrs, excl, h.Dynamic).Scan(&id); err != nil {
|
||||
return err
|
||||
}
|
||||
return bump(ctx, tx)
|
||||
})
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *Store) DeleteHost(ctx context.Context, id int64) error {
|
||||
return s.deleteOne(ctx, `DELETE FROM hosts WHERE id = $1`, id)
|
||||
}
|
||||
|
||||
// ---- Providers -------------------------------------------------------------
|
||||
|
||||
func (s *Store) ListProviders(ctx context.Context) ([]model.Provider, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT id, device, name, number, mark, duplicate, interface, gateway, copy FROM providers ORDER BY id`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []model.Provider
|
||||
for rows.Next() {
|
||||
p, err := scanProvider(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) GetProvider(ctx context.Context, id int64) (model.Provider, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT id, device, name, number, mark, duplicate, interface, gateway, copy FROM providers WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return model.Provider{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return model.Provider{}, ErrNotFound
|
||||
}
|
||||
return scanProvider(rows)
|
||||
}
|
||||
|
||||
func scanProvider(rows pgx.Rows) (model.Provider, error) {
|
||||
var p model.Provider
|
||||
var cp []byte
|
||||
if err := rows.Scan(&p.ID, &p.Device, &p.Name, &p.Number, &p.Mark, &p.Duplicate, &p.Interface, &p.Gateway, &cp); err != nil {
|
||||
return p, err
|
||||
}
|
||||
return p, unmarshalStrings(cp, &p.Copy)
|
||||
}
|
||||
|
||||
func (s *Store) CreateProvider(ctx context.Context, p model.Provider) (int64, error) {
|
||||
cp, _ := jsonb(p.Copy)
|
||||
var id int64
|
||||
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
||||
if err := tx.QueryRow(ctx, `
|
||||
INSERT INTO providers (device, name, number, mark, duplicate, interface, gateway, copy)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`,
|
||||
p.Device, p.Name, p.Number, p.Mark, p.Duplicate, p.Interface, p.Gateway, cp).Scan(&id); err != nil {
|
||||
return err
|
||||
}
|
||||
return bump(ctx, tx)
|
||||
})
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *Store) DeleteProvider(ctx context.Context, id int64) error {
|
||||
return s.deleteOne(ctx, `DELETE FROM providers WHERE id = $1`, id)
|
||||
}
|
||||
|
||||
// ---- Routes ----------------------------------------------------------------
|
||||
|
||||
func (s *Store) ListRoutes(ctx context.Context) ([]model.Route, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT id, device, provider, dest, gateway, oif, persistent, comment FROM routes ORDER BY id`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []model.Route
|
||||
for rows.Next() {
|
||||
var r model.Route
|
||||
if err := rows.Scan(&r.ID, &r.Device, &r.Provider, &r.Dest, &r.Gateway, &r.Oif, &r.Persistent, &r.Comment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) GetRoute(ctx context.Context, id int64) (model.Route, error) {
|
||||
var r model.Route
|
||||
err := s.pool.QueryRow(ctx,
|
||||
`SELECT id, device, provider, dest, gateway, oif, persistent, comment FROM routes WHERE id = $1`, id,
|
||||
).Scan(&r.ID, &r.Device, &r.Provider, &r.Dest, &r.Gateway, &r.Oif, &r.Persistent, &r.Comment)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return r, ErrNotFound
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (s *Store) CreateRoute(ctx context.Context, r model.Route) (int64, error) {
|
||||
var id int64
|
||||
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
||||
if err := tx.QueryRow(ctx, `
|
||||
INSERT INTO routes (device, provider, dest, gateway, oif, persistent, comment)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,
|
||||
r.Device, r.Provider, r.Dest, r.Gateway, r.Oif, r.Persistent, r.Comment).Scan(&id); err != nil {
|
||||
return err
|
||||
}
|
||||
return bump(ctx, tx)
|
||||
})
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *Store) DeleteRoute(ctx context.Context, id int64) error {
|
||||
return s.deleteOne(ctx, `DELETE FROM routes WHERE id = $1`, id)
|
||||
}
|
||||
|
||||
// ---- Routing rules ---------------------------------------------------------
|
||||
|
||||
func (s *Store) ListRoutingRules(ctx context.Context) ([]model.RoutingRule, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT id, device, source, dest, provider, priority, persistent, mark, comment FROM routing_rules ORDER BY priority, id`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []model.RoutingRule
|
||||
for rows.Next() {
|
||||
var r model.RoutingRule
|
||||
if err := rows.Scan(&r.ID, &r.Device, &r.Source, &r.Dest, &r.Provider, &r.Priority, &r.Persistent, &r.Mark, &r.Comment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) GetRoutingRule(ctx context.Context, id int64) (model.RoutingRule, error) {
|
||||
var r model.RoutingRule
|
||||
err := s.pool.QueryRow(ctx,
|
||||
`SELECT id, device, source, dest, provider, priority, persistent, mark, comment FROM routing_rules WHERE id = $1`, id,
|
||||
).Scan(&r.ID, &r.Device, &r.Source, &r.Dest, &r.Provider, &r.Priority, &r.Persistent, &r.Mark, &r.Comment)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return r, ErrNotFound
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (s *Store) CreateRoutingRule(ctx context.Context, r model.RoutingRule) (int64, error) {
|
||||
var id int64
|
||||
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
||||
if err := tx.QueryRow(ctx, `
|
||||
INSERT INTO routing_rules (device, source, dest, provider, priority, persistent, mark, comment)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`,
|
||||
r.Device, r.Source, r.Dest, r.Provider, r.Priority, r.Persistent, r.Mark, r.Comment).Scan(&id); err != nil {
|
||||
return err
|
||||
}
|
||||
return bump(ctx, tx)
|
||||
})
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *Store) DeleteRoutingRule(ctx context.Context, id int64) error {
|
||||
return s.deleteOne(ctx, `DELETE FROM routing_rules WHERE id = $1`, id)
|
||||
}
|
||||
Reference in New Issue
Block a user