Files

70 lines
2.5 KiB
Go

package store
import (
"context"
"fmt"
"strings"
"gitea.mynodes.xyz/ilker/llmgw/internal/secrets"
)
// Open picks a backend from what the address looks like.
//
// A path is SQLite and a postgres:// URL is PostgreSQL, so an existing
// configuration keeps working untouched and moving to PostgreSQL is one line
// changed rather than a new setting to discover. A gateway on one machine has
// no reason to run a database server, and a gateway that has outgrown that
// should not have to be reconfigured twice.
//
// An address of the form @/path/to/file reads the real address out of that
// file, which is how a DSN carrying a password stays out of a configuration
// file that gets copied, diffed and pasted into issues.
func Open(ctx context.Context, address string) (*DB, error) {
address, err := resolveAddress(address)
if err != nil {
return nil, err
}
if isPostgres(address) {
return OpenPostgres(ctx, address)
}
return OpenSQLite(ctx, address)
}
// isPostgres reports whether an address names a PostgreSQL server.
//
// By scheme, because that is the only part of a DSN that is reliably there.
// A bare host:port would be ambiguous with a relative file path, so it is not
// accepted: an address this cannot classify should fail loudly rather than
// quietly create a SQLite file named after someone's database server.
func isPostgres(address string) bool {
return strings.HasPrefix(address, "postgres://") ||
strings.HasPrefix(address, "postgresql://")
}
// resolveAddress expands an @file indirection.
//
// The file is read whole and trimmed, so a trailing newline — which every
// editor and every `echo` adds — does not become part of a password.
func resolveAddress(address string) (string, error) {
if !strings.HasPrefix(address, "@") {
return address, nil
}
path := strings.TrimPrefix(address, "@")
// A file, or env: or vault: (the secrets package says how).
b, err := secrets.Read(context.Background(), path)
if err != nil {
return "", fmt.Errorf("reading the store address from %s: %w", path, err)
}
out := strings.TrimSpace(string(b))
if out == "" {
return "", fmt.Errorf("%s is empty, so there is no store address in it", path)
}
return out, nil
}
// ResolveAddress expands an @file store address, as Open does.
func ResolveAddress(address string) (string, error) { return resolveAddress(address) }
// IsPostgres reports whether a resolved address names a PostgreSQL server.
func IsPostgres(address string) bool { return isPostgres(address) }