Two things that were only ever confusing, and one of them stopped a command working on the cluster. The store type was named SQLite and kept the name when PostgreSQL was added, so OpenPostgres returned a *SQLite and all seventy-odd methods on the production store were spelled after the database it was not running on. Nothing broke; people did. Earlier today I added three methods to *SQLite for a table that on this cluster only ever lives in PostgreSQL, and the name did not once look wrong. It is a field, which is all it ever was. `llmgw users` and `llmgw keys` defaulted --store to a SQLite path. On a deployment whose store is PostgreSQL every one of them failed with "unable to open database file" while the right answer sat in auth.store in the config file beside them; the conclusion an operator draws is that the tool is broken, and they are not far off. They now ask the server's own configuration before falling back, and the error names which of the three sources chose the address — the failure was never the path, it was which one picked it. The single-machine default still applies last, so a deployment with no config file keeps working with no flags. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
77 lines
2.8 KiB
Go
77 lines
2.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/config"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
|
|
)
|
|
|
|
// defaultStorePath is where a single-machine deployment keeps its store.
|
|
const defaultStorePath = "/var/lib/llmgw/llmgw.db"
|
|
|
|
// defaultConfigPath is where the control plane's configuration usually lives.
|
|
//
|
|
// Consulted rather than required. The commands that work on the store run on
|
|
// the same machine as the server far more often than not, and the server
|
|
// already knows where the store is — asking it is better than making the
|
|
// operator repeat an answer that is written down three lines away.
|
|
const defaultConfigPath = "/etc/llmgw/server.yaml"
|
|
|
|
// storeFlags is how a command that works directly on the store finds it.
|
|
type storeFlags struct {
|
|
address string
|
|
config string
|
|
}
|
|
|
|
// bind adds --store and --config to a command.
|
|
func (f *storeFlags) bind(cmd *cobra.Command) {
|
|
cmd.PersistentFlags().StringVar(&f.address, "store", "",
|
|
"store address: a SQLite path, a postgres:// DSN, or @file holding one "+
|
|
"(default: auth.store from --config, else "+defaultStorePath+")")
|
|
cmd.PersistentFlags().StringVar(&f.config, "config", defaultConfigPath,
|
|
"server configuration to read the store address from")
|
|
}
|
|
|
|
// open resolves the store and opens it.
|
|
//
|
|
// Explicit --store wins; otherwise the server's own configuration is asked,
|
|
// and only then does the single-machine default apply. That order exists
|
|
// because the default was a SQLite path and nothing else: on a cluster whose
|
|
// store is PostgreSQL, every one of these commands failed with "unable to open
|
|
// database file" while the right answer sat in the config file beside it. The
|
|
// operator's conclusion was that the tool was broken, and they were not far off.
|
|
func (f *storeFlags) open(cmd *cobra.Command) (*store.DB, error) {
|
|
return f.openCtx(cmd.Context())
|
|
}
|
|
|
|
// openCtx is open for a caller that has a context rather than a command.
|
|
func (f *storeFlags) openCtx(ctx context.Context) (*store.DB, error) {
|
|
address, from := f.resolve()
|
|
s, err := store.Open(ctx, address)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opening the store at %s (%s): %w", address, from, err)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// resolve returns the address and where it came from, for the error message.
|
|
//
|
|
// Where it came from is half the message. "unable to open database file" says
|
|
// nothing about which of three sources chose that path, and the whole problem
|
|
// is that the wrong one did.
|
|
func (f *storeFlags) resolve() (address, from string) {
|
|
if f.address != "" {
|
|
return f.address, "--store"
|
|
}
|
|
if f.config != "" {
|
|
if cfg, err := config.Load(f.config); err == nil && cfg.Auth.Store != "" {
|
|
return cfg.Auth.Store, "auth.store in " + f.config
|
|
}
|
|
}
|
|
return defaultStorePath, "the default; no --store and nothing usable in " + f.config
|
|
}
|