Documentation
¶
Overview ¶
Package tailsql implements an HTTP API and "playground" UI for sending SQL queries to a collection of local and/or remote databases, and rendering the results for human consumption.
API ¶
The main UI is served from "/", static assets from "/static/". The following path and query parameters are understood:
The q parameter carries an SQL query. The syntax of the query depends on the src (see below). See also "Named Queries" below.
The src parameter names which database to query against. Its values are defined when the server is set up. If src is omitted, the first database is used as a default.
"/" serves output as HTML for the UI. In this format the query (q) may be empty (no output will be displayed).
"/json" serves output as JSON objects, one per row. In this format the query (q) must be non-empty.
"/csv" serves output as comma-separated values, the first line giving the column names, the remaining lines the rows. In this format the query (q) must be non-empty.
"/meta" serves a JSON blob of metadata about available data sources.
Calls to the /json endpoint must set the Sec-Tailsql header to "1". This prevents browser scripts from directing queries to this endpoint.
Calls to /csv must either set Sec-Tailsql to "1" or include a tailsqlQuery=1 same-site cookie.
Calls to the UI with a non-empty query must include the tailsqlQuery=1 same-site cookie, which is set when the UI first loads. This averts simple cross-site redirection tricks.
Named Queries ¶
The query processor treats a query of the form "named:<string>" as a named query. Named queries are SQL queries pre-defined by the database, to allow users to make semantically stable queries without relying on a specific schema format.
Meta Queries ¶
The query processor treats a query "meta:named" as a meta-query to report the names and content of all named queries, regardless of source.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func UnmarshalOptions ¶
UnmarshalOptions unmarshals a HuJSON Config value into opts.
Types ¶
type DBOptions ¶
type DBOptions struct {
// Label is a human-readable descriptive label to show to users when
// rendering this database in a UI.
Label string
// NamedQueries is a map from names to SQL query text, that the service
// should allow as pre-defined queries for this database.
//
// Unlike user-saved queries, named queries allow the database to change the
// query when the underlying schema changes while preserving the semantics
// the user observes.
NamedQueries map[string]string
}
DBOptions are optional settings for a database. A nil *DBoptions is ready for use and provides defaults as described.
type DBSpec ¶
type DBSpec struct {
Source string `json:"source"` // UI slug (required)
Label string `json:"label,omitempty"` // descriptive label
Driver string `json:"driver,omitempty"` // e.g., "sqlite", "snowflake"
// Named is an optional map of named SQL queries the database should expose.
Named map[string]string `json:"named,omitempty"`
URL string `json:"url,omitempty"` // path or connection URL
KeyFile string `json:"keyFile,omitempty"` // path to key file
Secret string `json:"secret,omitempty"` // name of secret
DB Queryable `json:"-"` // programmatic data source
}
A DBSpec describes a database that the server should use.
The Source must be non-empty, and exactly one of URL, KeyFile, Secret, or DB must be set.
- If DB is set, it is used directly as the database to query, and no connection is established.
Otherwise, the Driver must be non-empty and the database/sql library is used to open a connection to the specified database:
If URL is set, it is used directly as the connection string.
If KeyFile is set, it names the location of a file containing the connection string. If set, KeyFile is expanded by os.ExpandEnv.
Otherwise, Secret is the name of a secret to fetch from the secrets service, whose value is the connection string. This requires that a secrets server be configured in the options.
type Duration ¶
Duration is a wrapper for a time.Duration that allows it to marshal more legibly in JSON, using the standard Go notation.
func (Duration) MarshalText ¶
func (*Duration) UnmarshalText ¶
type LocalClient ¶
LocalClient is the subset of the tailscale.LocalClient interface required by the Server. It is defined here so that the caller can substitute a fake for testing and debugging.
type Options ¶
type Options struct {
// The tailnet hostname the server should run on (tsnet).
Hostname string `json:"hostname,omitempty"`
// The directory for tailscale state and configurations (tsnet).
StateDir string `json:"stateDir,omitempty"`
// If true, serve HTTPS instead of HTTP (tsnet).
ServeHTTPS bool `json:"serveHTTPS,omitempty"`
// The URL of the coordination server to use (tsnet).
// If empty, the Tailscale default is used.
ControlURL string `json:"controlURL,omitempty"`
// If non-empty, a SQLite database URL to use for local state.
LocalState string `json:"localState,omitempty"`
// If non-empty, and LocalState is defined, export a read-only copy of the
// local state database as a source with this name.
LocalSource string `json:"localSource,omitempty"`
// Databases that the server will allow queries against (optional).
Sources []DBSpec `json:"sources,omitempty"`
// Additional links that should be propagated to the UI.
UILinks []UILink `json:"links,omitempty"`
// If set, prepend this prefix to each HTTP route. By default, routes are
// anchored at "/".
RoutePrefix string `json:"routePrefix,omitempty"`
// The maximum timeout for a database query (0 means no timeout).
QueryTimeout Duration `json:"queryTimeout,omitempty"`
// A connection to tailscaled for authorization checks. If nil, no
// authorization checks are performed and all requests are permitted.
LocalClient LocalClient `json:"-"`
// If non-nil, the server will add metrics to this map. The caller is
// responsible for ensuring the map is published.
Metrics *expvar.Map `json:"-"`
// If non-nil and a LocalClient is available, Authorize is called for each
// request giving the requested database src and the caller's WhoIs record.
// If it reports an error, the request is failed.
//
// If Authorize is nil and a LocalClient is available, the default rule is
// to accept any logged-in user, rejecting tagged nodes.
//
// If no LocalClient is available, this field is ignored, no authorization
// checks are performed, and all requests are accepted.
Authorize func(src string, info *apitype.WhoIsResponse) error `json:"-"`
// If non-nil, use this store to fetch secret values. This is required if
// any of the sources specifies a named secret for its connection string.
SecretStore *setec.Store `json:"-"`
// Optional rules to apply when rendering text for presentation in the UI.
// After generating the value string, each rule is matched in order, and the
// first match (if any) is applied to rewrite the output. The value returned
// by the rule replaces the original string.
UIRewriteRules []UIRewriteRule `json:"-"`
// If non-nil, call this function with each query presented to the API. If
// the function reports an error, the query fails; otherwise the returned
// query state is used to service the query. If nil, DefaultCheckQuery is
// used.
CheckQuery func(Query) (Query, error) `json:"-"`
// If non-nil, send logs to this logger. If nil, use log.Printf.
Logf logger.Logf `json:"-"`
}
Options describes settings for a Server.
The fields marked as "tsnet" are not used directly by tailsql, but are provided for the convenience of a main program that wants to run the server under tsnet.
func (Options) CheckSources ¶
CheckSources validates the sources of o. If this succeeds, it also returns a slice of any secret names required by the specified sources, if any.
type Query ¶
type Query struct {
Source string // the data source requested
Query string // the text of the query
}
A Query carries the parameters of a query presented to the API.
func DefaultCheckQuery ¶
DefaultCheckQuery is the default query check function used if another is not specified in the Options. It accepts all queries for all sources, as long as the query text does not exceed 4000 bytes.
type Queryable ¶
type Queryable interface {
// Query issues the specified SQL query in a transaction and returns the
// matching result set, if any.
Query(ctx context.Context, sql string, params ...any) (RowSet, error)
// Close closes the database.
Close() error
}
Queryable is the interface used to issue SQL queries to a database.
type RowSet ¶
type RowSet interface {
// Columns reports the names of the columns requested by the query.
Columns() ([]string, error)
// Close closes the row set, preventing further enumeration.
Close() error
// Err returns the error, if any, that was encountered during iteration.
Err() error
// Next prepares the next result row for reading by the Scan method, and
// reports true if this was successful or false if there was an error or no
// more rows are available.
Next() bool
// Scan copies the columns of the currently-selected row into the values
// pointed to by its arguments.
Scan(...any) error
}
A RowSet is a sequence of rows reported by a query. It is a subset of the interface exposed by database/sql.Rows, and the implementation must provide the same semantics for each of these methods.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a server for the tailsql API.
func (*Server) Close ¶
Close closes all the database handles held by s and returns the join of their errors.
func (*Server) SetDB ¶
SetDB adds or replaces the database associated with the specified source in s with the given open db and options. See Server.SetSource.
func (*Server) SetSource ¶
SetSource adds or replaces the database associated with the specified source in s with the given open db and options.
If a database was already open for the given source, its value is replaced, the old database handle is closed, and SetDB reports true.
If no database was already open for the given source, a new source is added and SetSource reports false.
type UIRewriteRule ¶
type UIRewriteRule struct {
Column *regexp.Regexp // pattern for the column name (nil matches all)
Value *regexp.Regexp // pattern for the value (nil matches all)
// The Apply function takes the name of a column, the input value, and the
// result of matching the value regexp (if any). Its return value replaces
// the input when the value is rendered. If Apply == nil, the input is not
// modified.
//
// As a special case, if Apply returns a nil value, the rule evaluator skips
// the rule as if it had not matched, and goes on to the next rule.
Apply func(column, input string, valueMatch []string) any
}
UIRewriteRule is a rewriting rule for rendering output in HTML.
A rule matches a value if:
- Its Column regexp is empty or matches the column name, and
- Its Value regexp is empty or matches the value string
If a rule matches, its Apply function is called.
func (UIRewriteRule) CheckApply ¶
func (u UIRewriteRule) CheckApply(column, input string) (bool, any)
CheckApply reports whether u matches the specified column and input, and if so returns the result of applying u to it.