init --patroni user@host ... installs PostgreSQL, Patroni and etcd on the given machines over ssh, with this one as the third etcd vote, makes the role and database, and moves the store in use into the cluster. Each step checks first, so it changes nothing on a cluster that is up. For a store of several hosts the control plane publishes each member's role, state and lag from Patroni, a stopped member as missing, prints it in ctl status, and alerts on it. Making an administrator on PostgreSQL sent a bool to an integer column; it sends an integer now. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
45 lines
1.5 KiB
Go
45 lines
1.5 KiB
Go
package store
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// SplitPostgres reads a PostgreSQL address that may name several hosts —
|
|
// "postgres://u:p@h1:5432,h2:5432/db?…", the members of a replicated cluster,
|
|
// as pgx and libpq both accept — into its hosts, their ports, and the rest of
|
|
// the address with one placeholder host, which url.Parse can read. url.Parse
|
|
// cannot read the list itself: it took everything before the last colon for
|
|
// one host name.
|
|
func SplitPostgres(dsn string) (hosts, ports []string, rest *url.URL, err error) {
|
|
scheme, after, ok := strings.Cut(dsn, "://")
|
|
if !ok {
|
|
return nil, nil, nil, fmt.Errorf("the store address is not a URL")
|
|
}
|
|
authority, tail := after, ""
|
|
if i := strings.IndexAny(after, "/?"); i >= 0 {
|
|
authority, tail = after[:i], after[i:]
|
|
}
|
|
userinfo, hostlist := "", authority
|
|
if i := strings.LastIndexByte(authority, '@'); i >= 0 {
|
|
userinfo, hostlist = authority[:i+1], authority[i+1:]
|
|
}
|
|
for _, hp := range strings.Split(hostlist, ",") {
|
|
u, err := url.Parse("x://" + hp)
|
|
if err != nil || u.Hostname() == "" {
|
|
return nil, nil, nil, fmt.Errorf("the store address has a host that is not one: %q", hp)
|
|
}
|
|
port := u.Port()
|
|
if port == "" {
|
|
port = "5432"
|
|
}
|
|
hosts, ports = append(hosts, u.Hostname()), append(ports, port)
|
|
}
|
|
rest, err = url.Parse(scheme + "://" + userinfo + "host" + tail)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("the store address is not a URL")
|
|
}
|
|
return hosts, ports, rest, nil
|
|
}
|