223 lines
5.9 KiB
Go
223 lines
5.9 KiB
Go
package postgresadmin
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
|
|
"proxy-pool/internal/domain/adminstate"
|
|
)
|
|
|
|
func (adapter *adapter) Snapshot(ctx context.Context) (adminstate.Snapshot, error) {
|
|
if err := contextError(ctx); err != nil {
|
|
return adminstate.Snapshot{}, err
|
|
}
|
|
if !adapter.valid() {
|
|
return adminstate.Snapshot{}, adminstate.ErrInvalidCommand
|
|
}
|
|
tx, err := adapter.begin(ctx, pgx.TxOptions{
|
|
IsoLevel: pgx.RepeatableRead,
|
|
AccessMode: pgx.ReadOnly,
|
|
}, "begin snapshot")
|
|
if err != nil {
|
|
return adminstate.Snapshot{}, err
|
|
}
|
|
defer rollback(tx)
|
|
|
|
snapshot := adminstate.Snapshot{}
|
|
var revision int64
|
|
if err := tx.QueryRow(ctx, currentRevisionSQL).Scan(&revision); err != nil {
|
|
return adminstate.Snapshot{}, databaseError(ctx, "read snapshot revision", err)
|
|
}
|
|
convertedRevision, ok := domainID(revision)
|
|
if !ok {
|
|
return adminstate.Snapshot{}, unavailable("decode snapshot revision")
|
|
}
|
|
snapshot.Revision = convertedRevision
|
|
|
|
var configRevision int64
|
|
config := adminstate.ConfigRevision{}
|
|
err = tx.QueryRow(ctx, `
|
|
SELECT revision, config_version, checksum::text, source, created_at
|
|
FROM config_revisions
|
|
ORDER BY revision DESC
|
|
LIMIT 1`).Scan(
|
|
&configRevision,
|
|
&config.ConfigVersion,
|
|
&config.Checksum,
|
|
&config.Source,
|
|
&config.CreatedAt,
|
|
)
|
|
switch {
|
|
case err == nil:
|
|
converted, ok := domainID(configRevision)
|
|
if !ok {
|
|
return adminstate.Snapshot{}, unavailable("decode config revision")
|
|
}
|
|
config.Revision = converted
|
|
config.CreatedAt = utc(config.CreatedAt)
|
|
snapshot.Config = &config
|
|
case !errors.Is(err, pgx.ErrNoRows):
|
|
return adminstate.Snapshot{}, databaseError(ctx, "read config snapshot", err)
|
|
}
|
|
|
|
upstreams, err := readUpstreamSnapshot(ctx, tx)
|
|
if err != nil {
|
|
return adminstate.Snapshot{}, err
|
|
}
|
|
snapshot.Upstreams = upstreams
|
|
routings, err := readRoutingSnapshot(ctx, tx)
|
|
if err != nil {
|
|
return adminstate.Snapshot{}, err
|
|
}
|
|
snapshot.Routings = routings
|
|
if err := commit(ctx, tx, "commit snapshot"); err != nil {
|
|
return adminstate.Snapshot{}, err
|
|
}
|
|
return snapshot, nil
|
|
}
|
|
|
|
func (adapter *adapter) ReadAudit(
|
|
ctx context.Context,
|
|
query adminstate.AuditQuery,
|
|
) ([]adminstate.AuditRecord, error) {
|
|
if err := contextError(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if query.Validate() != nil || !adapter.valid() {
|
|
return nil, adminstate.ErrInvalidCommand
|
|
}
|
|
afterID, ok := databaseID(query.AfterID)
|
|
if !ok {
|
|
return []adminstate.AuditRecord{}, nil
|
|
}
|
|
tx, err := adapter.begin(ctx, pgx.TxOptions{AccessMode: pgx.ReadOnly}, "begin audit read")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rollback(tx)
|
|
|
|
rows, err := tx.Query(ctx, `
|
|
SELECT id, request_id, actor_id, COALESCE(host(source_ip), ''), action,
|
|
resource_type, resource_name, changed, revision, reason, occurred_at
|
|
FROM admin_audit_log
|
|
WHERE id > $1
|
|
ORDER BY id
|
|
LIMIT $2`, afterID, query.Limit)
|
|
if err != nil {
|
|
return nil, databaseError(ctx, "read audit page", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
records := make([]adminstate.AuditRecord, 0, query.Limit)
|
|
for rows.Next() {
|
|
record := adminstate.AuditRecord{}
|
|
var id int64
|
|
var revision int64
|
|
var action string
|
|
if err := rows.Scan(
|
|
&id,
|
|
&record.RequestID,
|
|
&record.Actor.ID,
|
|
&record.Actor.SourceIP,
|
|
&action,
|
|
&record.ResourceType,
|
|
&record.ResourceName,
|
|
&record.Changed,
|
|
&revision,
|
|
&record.Reason,
|
|
&record.OccurredAt,
|
|
); err != nil {
|
|
return nil, databaseError(ctx, "decode audit page", err)
|
|
}
|
|
convertedID, idOK := domainID(id)
|
|
convertedRevision, revisionOK := domainID(revision)
|
|
if !idOK || !revisionOK {
|
|
return nil, unavailable("decode audit page")
|
|
}
|
|
record.ID = convertedID
|
|
record.Revision = convertedRevision
|
|
record.Action = adminstate.Action(action)
|
|
record.OccurredAt = utc(record.OccurredAt)
|
|
records = append(records, record)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, databaseError(ctx, "read audit page", err)
|
|
}
|
|
if err := commit(ctx, tx, "commit audit read"); err != nil {
|
|
return nil, err
|
|
}
|
|
return records, nil
|
|
}
|
|
|
|
func readUpstreamSnapshot(ctx context.Context, tx pgx.Tx) ([]adminstate.UpstreamState, error) {
|
|
rows, err := tx.Query(ctx, `
|
|
SELECT name, enabled, revision, updated_at
|
|
FROM upstream_admin_state
|
|
ORDER BY name`)
|
|
if err != nil {
|
|
return nil, databaseError(ctx, "read upstream snapshot", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
states := make([]adminstate.UpstreamState, 0)
|
|
for rows.Next() {
|
|
state := adminstate.UpstreamState{}
|
|
var revision int64
|
|
if err := rows.Scan(&state.Name, &state.Enabled, &revision, &state.UpdatedAt); err != nil {
|
|
return nil, databaseError(ctx, "decode upstream snapshot", err)
|
|
}
|
|
converted, ok := domainID(revision)
|
|
if !ok {
|
|
return nil, unavailable("decode upstream snapshot")
|
|
}
|
|
state.Revision = converted
|
|
state.UpdatedAt = utc(state.UpdatedAt)
|
|
states = append(states, state)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, databaseError(ctx, "read upstream snapshot", err)
|
|
}
|
|
return states, nil
|
|
}
|
|
|
|
func readRoutingSnapshot(ctx context.Context, tx pgx.Tx) ([]adminstate.RoutingState, error) {
|
|
rows, err := tx.Query(ctx, `
|
|
SELECT name, enabled, upstreams, current_upstream, revision, updated_at
|
|
FROM routing_admin_state
|
|
ORDER BY name`)
|
|
if err != nil {
|
|
return nil, databaseError(ctx, "read routing snapshot", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
states := make([]adminstate.RoutingState, 0)
|
|
for rows.Next() {
|
|
state := adminstate.RoutingState{}
|
|
var revision int64
|
|
if err := rows.Scan(
|
|
&state.Name,
|
|
&state.Enabled,
|
|
&state.Upstreams,
|
|
&state.CurrentUpstream,
|
|
&revision,
|
|
&state.UpdatedAt,
|
|
); err != nil {
|
|
return nil, databaseError(ctx, "decode routing snapshot", err)
|
|
}
|
|
converted, ok := domainID(revision)
|
|
if !ok {
|
|
return nil, unavailable("decode routing snapshot")
|
|
}
|
|
state.Revision = converted
|
|
state.UpdatedAt = utc(state.UpdatedAt)
|
|
state.Upstreams = append([]string(nil), state.Upstreams...)
|
|
states = append(states, state)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, databaseError(ctx, "read routing snapshot", err)
|
|
}
|
|
return states, nil
|
|
}
|