feat(updater): 从 felis-postgres 容器读取 PostgreSQL 版本并改为随发布更新镜像
This commit is contained in:
7 files changed
+89
-35
No files matched your search
@@ -18,7 +18,8 @@ import (
|
||||
// database that already holds a schema and has migrations pending is bundled
|
||||
// first (internal/dbbackup, label pre-migrate). A failed snapshot stops the
|
||||
// upgrade; -no-backup is the explicit way past it, e.g. for an external
|
||||
// database whose server is newer than the host's pg_dump.
|
||||
// database (no [database] deployment, so the host's own pg_dump runs) whose
|
||||
// server is newer than that pg_dump.
|
||||
func cmdMigrate(args []string, stdout, stderr io.Writer) int {
|
||||
fs := flag.NewFlagSet("migrate", flag.ContinueOnError)
|
||||
fs.SetOutput(stderr)
|
||||
|
||||
+6
-4
@@ -128,8 +128,9 @@ var updateTargets = []updateTarget{
|
||||
selector: "postgres",
|
||||
help: "select PostgreSQL",
|
||||
component: "postgresql",
|
||||
note: "PostgreSQL comes from the distribution's packages, so a minor release is a package update followed by a restart (a few seconds without the API). A new major needs pg_upgrade first: docs/operations.md §4",
|
||||
command: "sudo dnf upgrade 'postgresql*' || sudo apt-get install --only-upgrade 'postgresql*'; sudo systemctl restart postgresql",
|
||||
note: "PostgreSQL runs as the felis-postgres Deployment from the image the Felis release pins by digest; a newer minor reaches the host with a release that moves the pin, and the installer re-run restarts the database on it (a few seconds without the API). A new major is a dump and restore: docs/operations.md §4",
|
||||
command: installerRerun,
|
||||
installer: true,
|
||||
},
|
||||
{
|
||||
selector: "mc",
|
||||
@@ -149,8 +150,9 @@ var updateTargets = []updateTarget{
|
||||
// reinstall/repair case.
|
||||
//
|
||||
// It never applies anything and never mutates the node, so unlike setup/breakGlass
|
||||
// it needs no root. The versions it reads come from this host: k3s, cloudflared and
|
||||
// PostgreSQL answer `--version`, Velocity's version is read out of the installed jar's
|
||||
// it needs no root, apart from PostgreSQL's version, which the felis-postgres container
|
||||
// answers through the cluster's admin kubeconfig. The versions it reads come from this
|
||||
// host: k3s, cloudflared and PostgreSQL answer `--version`, Velocity's version is read out of the installed jar's
|
||||
// manifest, the JRE's out of its release file, and felis-api's is this binary's own
|
||||
// build stamp — the same value `felis version` prints, which is what the user asked
|
||||
// to be the source of truth.
|
||||
|
||||
@@ -215,8 +215,8 @@ func TestUpdateSelectorsAreFlags(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// k3s and cloudflared move only when the re-run is told to; PostgreSQL is the package
|
||||
// manager's, so its guidance carries no installer trailer.
|
||||
// k3s and cloudflared move only when the re-run is told to; the release pins the JRE
|
||||
// build and the PostgreSQL image, so their guidance is the plain re-run.
|
||||
func TestApplyGuidanceForHostDependencies(t *testing.T) {
|
||||
notify := func(c string) updater.Result {
|
||||
return planResult([]updates.Action{{Component: c, Kind: updates.ActionNotify, LatestKnown: true}})
|
||||
@@ -232,8 +232,8 @@ func TestApplyGuidanceForHostDependencies(t *testing.T) {
|
||||
t.Errorf("--jre guidance is the plain installer re-run:\n%s", jre)
|
||||
}
|
||||
pg := renderApplyGuidance(notify("postgresql"), map[string]bool{"postgres": true}, false)
|
||||
if !strings.Contains(pg, "apt-get install --only-upgrade") || strings.Contains(pg, "Re-running the installer") {
|
||||
t.Errorf("--postgres guidance is the package manager, without the installer trailer:\n%s", pg)
|
||||
if !strings.Contains(pg, "| sudo bash") || strings.Contains(pg, "FELIS_UPGRADE_DEPS") || strings.Contains(pg, "apt-get") || strings.Contains(pg, "systemctl") {
|
||||
t.Errorf("--postgres guidance is the plain installer re-run that moves the image pin:\n%s", pg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"felis.lolicon.best/internal/platform"
|
||||
"felis.lolicon.best/internal/updates"
|
||||
)
|
||||
|
||||
@@ -86,18 +87,39 @@ func (g hostGatherer) Current(ctx context.Context, spec Spec) (updates.Version,
|
||||
case "jre":
|
||||
return jreReleaseVersion(g.jreRelease)
|
||||
case "postgresql":
|
||||
// The server binary is on PATH on the dnf family; Debian and Ubuntu keep it
|
||||
// under /usr/lib/postgresql/<major>/bin and put only the client on PATH, which
|
||||
// the distribution ships at the same version.
|
||||
if v, err := g.sys.cliVersion(ctx, "postgres"); err == nil {
|
||||
return v, nil
|
||||
}
|
||||
return g.sys.cliVersion(ctx, "psql")
|
||||
return g.postgresVersion(ctx)
|
||||
default:
|
||||
return g.sys.Current(ctx, spec)
|
||||
}
|
||||
}
|
||||
|
||||
// postgresVersionArgv asks the server binary in the felis-postgres Deployment
|
||||
// (internal/platform/postgres.go) for its version. The database runs from the image
|
||||
// the release pins, so the answer comes from the container: a distribution package
|
||||
// the move into k3s left installed on the host answers with a version nothing runs.
|
||||
var postgresVersionArgv = []string{
|
||||
"kubectl", "exec", "-n", platform.DefaultControlNamespace, "deploy/" + platform.PostgresName,
|
||||
"-c", platform.PostgresContainer, "--", "postgres", "--version",
|
||||
}
|
||||
|
||||
func (g hostGatherer) postgresVersion(ctx context.Context) (updates.Version, error) {
|
||||
if g.sys.run == nil {
|
||||
return updates.Version{}, fmt.Errorf("updater: command runner not wired for %s", platform.PostgresName)
|
||||
}
|
||||
out, err := g.sys.run.output(ctx, "k3s", postgresVersionArgv...)
|
||||
if err != nil {
|
||||
if msg := truncate(string(out), 200); msg != "" {
|
||||
err = fmt.Errorf("%w: %s", err, msg)
|
||||
}
|
||||
return updates.Version{}, fmt.Errorf("updater: ask %s for its version: %w", platform.PostgresName, err)
|
||||
}
|
||||
v, err := versionFromCLI(string(out))
|
||||
if err != nil {
|
||||
return updates.Version{}, fmt.Errorf("updater: %s: %w", platform.PostgresName, err)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// jreReleaseVersion reads the runtime's version from its release file. Temurin writes
|
||||
// SEMANTIC_VERSION="25.0.4.1+1"; JAVA_VERSION="25.0.4.1" is the fallback every JDK
|
||||
// build writes. JAVA_RUNTIME_VERSION is avoided: its "-LTS" tail reads as a prerelease.
|
||||
|
||||
@@ -3,8 +3,11 @@ package updater
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"felis.lolicon.best/internal/updates"
|
||||
@@ -137,23 +140,47 @@ func TestJREReleaseVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// PostgreSQL answers from the server binary where it is on PATH, else from the client.
|
||||
// argvCmd answers one exact command line and records what it was asked.
|
||||
type argvCmd struct {
|
||||
want string
|
||||
out []byte
|
||||
err error
|
||||
got *[]string
|
||||
}
|
||||
|
||||
func (a argvCmd) output(_ context.Context, name string, args ...string) ([]byte, error) {
|
||||
line := strings.Join(append([]string{name}, args...), " ")
|
||||
*a.got = append(*a.got, line)
|
||||
if line != a.want {
|
||||
return nil, fmt.Errorf("unexpected command %q", line)
|
||||
}
|
||||
return a.out, a.err
|
||||
}
|
||||
|
||||
// PostgreSQL answers from the server binary in the felis-postgres container, never
|
||||
// from a host package the move into k3s left installed.
|
||||
func TestHostGathererPostgres(t *testing.T) {
|
||||
for name, out := range map[string]map[string][]byte{
|
||||
"server on PATH": {"postgres": []byte("postgres (PostgreSQL) 13.23\n"), "psql": []byte("psql (PostgreSQL) 12.1\n")},
|
||||
"client only": {"psql": []byte("psql (PostgreSQL) 13.23 (Ubuntu 13.23-1.pgdg24.04+1)\n")},
|
||||
} {
|
||||
g := hostGatherer{sys: sysGatherer{run: fakeCmd{out: out}}}
|
||||
const exec = "k3s kubectl exec -n felis deploy/felis-postgres -c postgres -- postgres --version"
|
||||
var got []string
|
||||
g := hostGatherer{sys: sysGatherer{run: argvCmd{want: exec, out: []byte("postgres (PostgreSQL) 18.6 (Debian 18.6-1.pgdg13+1)\n"), got: &got}}}
|
||||
v, err := g.Current(context.Background(), Spec{Name: "postgresql"})
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
t.Fatalf("Current: %v (ran %q)", err, got)
|
||||
}
|
||||
if v.String() != "13.23" {
|
||||
t.Errorf("%s: version = %s, want 13.23", name, v)
|
||||
if v.String() != "18.6" {
|
||||
t.Errorf("version = %s, want 18.6", v)
|
||||
}
|
||||
if len(got) != 1 {
|
||||
t.Errorf("ran %q, want exactly the exec into the database pod", got)
|
||||
}
|
||||
g := hostGatherer{sys: sysGatherer{run: fakeCmd{out: map[string][]byte{}}}}
|
||||
if _, err := g.Current(context.Background(), Spec{Name: "postgresql"}); err == nil {
|
||||
t.Error("no postgres and no psql must be an error")
|
||||
|
||||
got = nil
|
||||
down := hostGatherer{sys: sysGatherer{run: argvCmd{want: exec, out: []byte("error: no running pod\n"), err: errors.New("exit status 1"), got: &got}}}
|
||||
_, err = down.Current(context.Background(), Spec{Name: "postgresql"})
|
||||
if err == nil || !strings.Contains(err.Error(), "no running pod") {
|
||||
t.Errorf("a database pod that cannot answer = %v, want an error carrying kubectl's reason", err)
|
||||
}
|
||||
if len(got) != 1 {
|
||||
t.Errorf("ran %q after the pod failed, want no fallback to host binaries", got)
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,12 @@ import (
|
||||
// major, e.g. {"major":"13","latestMinor":"23","supported":false,"eolDate":"2025-11-13"}.
|
||||
const postgresFeedURL = "https://www.postgresql.org/versions.json"
|
||||
|
||||
// postgresFeed discovers the newest minor release of the host's PostgreSQL major.
|
||||
// postgresFeed discovers the newest minor release of the running PostgreSQL major.
|
||||
//
|
||||
// Felis installs PostgreSQL from the distribution, so the major is whatever the
|
||||
// distribution ships and moving it is a pg_upgrade the operator plans. The report
|
||||
// therefore compares within the major (a minor release is a package update), and a
|
||||
// major that is past its end of life is surfaced separately as a note.
|
||||
// Felis runs PostgreSQL from an image the release pins, and moving to a new major is a
|
||||
// dump and restore the operator plans. The report therefore compares within the major
|
||||
// (a minor release is a new pin), and a major that is past its end of life is surfaced
|
||||
// separately as a note.
|
||||
type postgresFeed struct {
|
||||
url string
|
||||
userAgent string
|
||||
|
||||
@@ -44,8 +44,10 @@ type Spec struct {
|
||||
// come from PaperMC (Fill v3), not GitHub.
|
||||
// - jre — the Temurin runtime Velocity runs on. The installer pins its patch
|
||||
// build, so a newer build reaches a host through a Felis release: Notify only.
|
||||
// - postgresql — the control-plane database, from the distribution's packages.
|
||||
// Notify only; a minor release is a package update, a major one a pg_upgrade.
|
||||
// - postgresql — the control-plane database, the felis-postgres Deployment running
|
||||
// the image the installer pins by digest. Notify only: a newer minor reaches a
|
||||
// host through a Felis release that moves the pin, a major one through a dump and
|
||||
// restore.
|
||||
//
|
||||
// Minecraft is deliberately ABSENT: every MC server is Pinned and is appended to the
|
||||
// plan at runtime from the live fleet (integration), never force-tracked here.
|
||||
|
||||
Reference in new issue
Block a user