Skip to content

Commit 3746a1e

Browse files
authored
Merge pull request #5 from consolving/pg16-and-bootstrap-fixes
PostgreSQL 16 migration, bootstrap fixes, and Ubuntu Noble export-tuf support
2 parents e819822 + fbf2929 commit 3746a1e

8 files changed

Lines changed: 451 additions & 92 deletions

File tree

‎appliance/postgresql/cmd/flynn-postgres-api/main.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import (
66
"os"
77
"strings"
88

9+
"context"
910
"github.com/flynn/flynn/discoverd/client"
1011
"github.com/flynn/flynn/pkg/httphelper"
1112
"github.com/flynn/flynn/pkg/postgres"
1213
"github.com/flynn/flynn/pkg/random"
1314
"github.com/flynn/flynn/pkg/resource"
1415
"github.com/flynn/flynn/pkg/shutdown"
1516
"github.com/julienschmidt/httprouter"
16-
"context"
1717
)
1818

1919
const (
@@ -78,7 +78,7 @@ func (p *pgAPI) createDatabase(ctx context.Context, w http.ResponseWriter, req *
7878
httphelper.Error(w, err)
7979
return
8080
}
81-
if err := p.db.Exec(fmt.Sprintf(`CREATE DATABASE "%s"`, database)); err != nil {
81+
if err := p.db.Exec(fmt.Sprintf(`CREATE DATABASE "%s" OWNER "%s"`, database, username)); err != nil {
8282
p.db.Exec(fmt.Sprintf(`DROP USER "%s"`, username))
8383
httphelper.Error(w, err)
8484
return

‎appliance/postgresql/process.go‎

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,9 @@ func (p *Process) assumePrimary(downstream *discoverd.Instance) (err error) {
347347
if p.running() && p.config().Role == state.RoleSync {
348348
log.Info("promoting to primary")
349349

350-
if err := os.WriteFile(p.triggerPath(), nil, 0655); err != nil {
351-
log.Error("error creating trigger file", "path", p.triggerPath(), "err", err)
350+
// PG 16 removed promote_trigger_file; use pg_ctl promote instead
351+
if err := p.runCmd(exec.Command(p.binPath("pg_ctl"), "promote", "-D", p.dataDir)); err != nil {
352+
log.Error("error promoting standby", "err", err)
352353
return err
353354
}
354355

@@ -369,6 +370,10 @@ func (p *Process) assumePrimary(downstream *discoverd.Instance) (err error) {
369370

370371
if err := os.Remove(p.recoveryConfPath()); err != nil && !os.IsNotExist(err) {
371372
log.Error("error removing recovery.conf", "path", p.recoveryConfPath(), "err", err)
373+
// non-fatal, PG 16 doesn't use recovery.conf
374+
}
375+
if err := os.Remove(p.standbySignalPath()); err != nil && !os.IsNotExist(err) {
376+
log.Error("error removing standby.signal", "path", p.standbySignalPath(), "err", err)
372377
return err
373378
}
374379

@@ -483,6 +488,14 @@ func (p *Process) installExtensionsInTemplate() error {
483488
return fmt.Errorf("creating extension %s in template1: %s", ext, err)
484489
}
485490
}
491+
492+
// In PostgreSQL 15+, the default CREATE privilege on the public schema was
493+
// revoked for non-owner roles. Restore the PG14 behavior so that application
494+
// users can create tables in the public schema of their databases.
495+
if _, err := templateDB.Exec("GRANT ALL ON SCHEMA public TO PUBLIC"); err != nil {
496+
return fmt.Errorf("granting public schema privileges in template1: %s", err)
497+
}
498+
486499
return nil
487500
}
488501

@@ -536,12 +549,8 @@ func (p *Process) assumeStandby(upstream, downstream *discoverd.Instance) error
536549
os.Remove(p.triggerPath())
537550
}
538551

539-
if err := p.writeConfig(configData{ReadOnly: true}); err != nil {
540-
log.Error("error writing postgres.conf", "path", p.configPath(), "err", err)
541-
return err
542-
}
543-
if err := p.writeRecoveryConf(upstream); err != nil {
544-
log.Error("error writing recovery.conf", "path", p.recoveryConfPath(), "err", err)
552+
if err := p.writeRecoveryConf(upstream, configData{ReadOnly: true}); err != nil {
553+
log.Error("error writing recovery config", "path", p.configPath(), "err", err)
545554
return err
546555
}
547556

@@ -897,21 +906,21 @@ func (p *Process) writeConfig(d configData) error {
897906
return configTemplate.Execute(f, d)
898907
}
899908

900-
func (p *Process) writeRecoveryConf(upstream *discoverd.Instance) error {
901-
data := recoveryData{
902-
TriggerFile: p.triggerPath(),
903-
PrimaryInfo: fmt.Sprintf(
904-
"host=%s port=%s user=flynn password=%s application_name=%s",
905-
upstream.Host(), upstream.Port(), p.password, p.id,
906-
),
907-
}
909+
func (p *Process) recoveryConfigData(upstream *discoverd.Instance, d configData) configData {
910+
d.PrimaryConnInfo = fmt.Sprintf(
911+
"host=%s port=%s user=flynn password=%s application_name=%s",
912+
upstream.Host(), upstream.Port(), p.password, p.id,
913+
)
914+
return d
915+
}
908916

909-
f, err := os.Create(p.recoveryConfPath())
910-
if err != nil {
917+
func (p *Process) writeRecoveryConf(upstream *discoverd.Instance, d configData) error {
918+
// PG 12+ uses postgresql.conf for recovery settings + standby.signal file
919+
if err := p.writeConfig(p.recoveryConfigData(upstream, d)); err != nil {
911920
return err
912921
}
913-
defer f.Close()
914-
return recoveryConfTemplate.Execute(f, data)
922+
// Create standby.signal to indicate this is a standby
923+
return os.WriteFile(p.standbySignalPath(), nil, 0644)
915924
}
916925

917926
func (p *Process) writeHBAConf() error {
@@ -923,9 +932,14 @@ func (p *Process) configPath() string {
923932
}
924933

925934
func (p *Process) recoveryConfPath() string {
935+
// PG 16 no longer uses recovery.conf; keep this for cleanup of old files
926936
return p.dataPath("recovery.conf")
927937
}
928938

939+
func (p *Process) standbySignalPath() string {
940+
return p.dataPath("standby.signal")
941+
}
942+
929943
func (p *Process) hbaConfPath() string {
930944
return p.dataPath("pg_hba.conf")
931945
}
@@ -951,6 +965,9 @@ type configData struct {
951965
TimescaleDB bool
952966
ExtWhitelist bool
953967
SHMType string
968+
969+
// Recovery settings (PG 12+ uses postgresql.conf instead of recovery.conf)
970+
PrimaryConnInfo string
954971
}
955972

956973
var configTemplate = template.Must(template.New("postgresql.conf").Parse(`
@@ -960,10 +977,10 @@ port = {{.Port}}
960977
ssl = off
961978
max_connections = 400
962979
shared_buffers = 32MB
963-
wal_level = hot_standby
980+
wal_level = replica
964981
fsync = on
965982
max_wal_senders = 15
966-
wal_keep_segments = 128
983+
wal_keep_size = 2048
967984
synchronous_commit = remote_write
968985
synchronous_standby_names = '{{.Sync}}'
969986
{{if .ReadOnly}}
@@ -985,6 +1002,7 @@ datestyle = 'iso, mdy'
9851002
timezone = 'UTC'
9861003
client_encoding = 'UTF8'
9871004
default_text_search_config = 'pg_catalog.english'
1005+
password_encryption = md5
9881006
9891007
{{if .TimescaleDB}}
9901008
shared_preload_libraries = 'timescaledb'
@@ -998,18 +1016,12 @@ dynamic_shared_memory_type = '{{.SHMType}}'
9981016
local_preload_libraries = 'pgextwlist'
9991017
extwlist.extensions = 'btree_gin,btree_gist,chkpass,citext,cube,dblink,dict_int,earthdistance,fuzzystrmatch,hstore,intarray,isn,ltree,pg_prewarm,pg_stat_statements,pg_trgm,pgcrypto,pgrouting,pgrowlocks,pgstattuple,plpgsql,plv8,postgis,postgis_topology,postgres_fdw,tablefunc,timescaledb,unaccent,uuid-ossp'
10001018
{{end}}
1001-
`[1:]))
1002-
1003-
type recoveryData struct {
1004-
PrimaryInfo string
1005-
TriggerFile string
1006-
}
10071019
1008-
var recoveryConfTemplate = template.Must(template.New("recovery.conf").Parse(`
1009-
standby_mode = on
1010-
primary_conninfo = '{{.PrimaryInfo}}'
1011-
trigger_file = '{{.TriggerFile}}'
1020+
{{if .PrimaryConnInfo}}
1021+
# Recovery settings (managed by flynn-postgres, PG 12+)
1022+
primary_conninfo = '{{.PrimaryConnInfo}}'
10121023
recovery_target_timeline = 'latest'
1024+
{{end}}
10131025
`[1:]))
10141026

10151027
var hbaConf = []byte(`

‎controller/data/schema.go‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ $$ LANGUAGE plpgsql`,
142142
created_at timestamptz NOT NULL DEFAULT now(),
143143
updated_at timestamptz NOT NULL DEFAULT now()
144144
)`,
145-
`CREATE FUNCTION check_job_state() RETURNS OPAQUE AS $$
145+
`CREATE FUNCTION check_job_state() RETURNS trigger AS $$
146146
BEGIN
147147
IF NEW.state < OLD.state THEN
148148
RAISE EXCEPTION 'invalid job state transition: % -> %', OLD.state, NEW.state USING ERRCODE = 'check_violation';
@@ -309,7 +309,7 @@ $$ LANGUAGE plpgsql`,
309309

310310
// add a check to ensure releases only have a single "docker"
311311
// artifact, and that artifact is added first
312-
`CREATE FUNCTION check_release_artifacts() RETURNS OPAQUE AS $$
312+
`CREATE FUNCTION check_release_artifacts() RETURNS trigger AS $$
313313
BEGIN
314314
IF (
315315
SELECT COUNT(*)
@@ -359,7 +359,7 @@ $$ LANGUAGE plpgsql`,
359359
`INSERT INTO event_types (name) VALUES ('release_deletion')`,
360360

361361
// add a trigger to prevent current app releases from being deleted
362-
`CREATE FUNCTION check_release_delete() RETURNS OPAQUE AS $$
362+
`CREATE FUNCTION check_release_delete() RETURNS trigger AS $$
363363
BEGIN
364364
IF NEW.deleted_at IS NOT NULL AND (SELECT COUNT(*) FROM apps WHERE release_id = NEW.release_id) != 0 THEN
365365
RAISE EXCEPTION 'cannot delete current app release' USING ERRCODE = 'check_violation';
@@ -432,7 +432,7 @@ $$ LANGUAGE plpgsql`,
432432
`ALTER TABLE artifacts ADD COLUMN hashes jsonb`,
433433
`ALTER TABLE artifacts ADD COLUMN size integer`,
434434
`ALTER TABLE artifacts ADD COLUMN layer_url_template text`,
435-
`CREATE FUNCTION check_artifact_manifest() RETURNS OPAQUE AS $$
435+
`CREATE FUNCTION check_artifact_manifest() RETURNS trigger AS $$
436436
BEGIN
437437
IF NEW.type = 'flynn' AND NEW.manifest IS NULL THEN
438438
RAISE EXCEPTION 'flynn artifacts must have a manifest' USING ERRCODE = 'check_violation';

‎controller/schema/schema.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func Load(schemaRoot string) error {
2323

2424
var schemaPaths []string
2525
walkFn := func(path string, info os.FileInfo, err error) error {
26-
if !info.IsDir() && filepath.Ext(path) == ".json" {
26+
if !info.IsDir() && filepath.Ext(path) == ".json" && !strings.HasPrefix(filepath.Base(path), "._") {
2727
schemaPaths = append(schemaPaths, path)
2828
}
2929
return nil

‎flannel/backend/vxlan/device.go‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ type vxlanDeviceAttrs struct {
2020
}
2121

2222
type vxlanDevice struct {
23-
link *netlink.Vxlan
23+
link *netlink.Vxlan
24+
desiredMAC net.HardwareAddr
2425
}
2526

2627
func newVXLANDevice(devAttrs *vxlanDeviceAttrs) (*vxlanDevice, error) {
@@ -79,7 +80,8 @@ func newVXLANDevice(devAttrs *vxlanDeviceAttrs) (*vxlanDevice, error) {
7980
}
8081

8182
return &vxlanDevice{
82-
link: link,
83+
link: link,
84+
desiredMAC: link.HardwareAddr,
8385
}, nil
8486
}
8587

@@ -134,6 +136,20 @@ func (dev *vxlanDevice) Configure(ipn ip.IP4Net) error {
134136
return fmt.Errorf("failed to set interface %s to UP state: %s", dev.link.Attrs().Name, err)
135137
}
136138

139+
// Re-apply the desired MAC address after LinkSetUp, because some kernels
140+
// regenerate the VXLAN MAC when the interface transitions to UP state.
141+
if dev.desiredMAC != nil {
142+
current, _ := netlink.LinkByIndex(dev.link.Index)
143+
if current != nil && !macEqual(current.Attrs().HardwareAddr, dev.desiredMAC) {
144+
log.Infof("MAC changed after LinkSetUp (got %s, want %s), re-applying", current.Attrs().HardwareAddr, dev.desiredMAC)
145+
if err := netlink.LinkSetHardwareAddr(dev.link, dev.desiredMAC); err != nil {
146+
log.Warningf("failed to re-apply MAC on %s: %v", dev.link.Name, err)
147+
} else {
148+
dev.link.HardwareAddr = dev.desiredMAC
149+
}
150+
}
151+
}
152+
137153
// explicitly add a route since there might be a route for a subnet already
138154
// installed by Docker and then it won't get auto added
139155
route := netlink.Route{

‎host/libcontainer_backend.go‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"github.com/flynn/flynn/pkg/shutdown"
4040
"github.com/flynn/flynn/pkg/syslog/rfc5424"
4141
"github.com/flynn/flynn/pkg/term"
42+
"github.com/flynn/flynn/pkg/tufconfig"
4243
"github.com/flynn/flynn/pkg/tufutil"
4344
"github.com/flynn/flynn/pkg/verify"
4445
tuf "github.com/flynn/go-tuf/client"
@@ -117,6 +118,21 @@ func NewLibcontainerBackend(config *LibcontainerConfig) (Backend, error) {
117118
if err != nil {
118119
return nil, fmt.Errorf("error initializing TUF client: %s", err)
119120
}
121+
// Update the local TUF metadata from the remote repository
122+
// so that target lookups in Download() can find the targets.
123+
// Initialize root keys if this is a fresh local store.
124+
if _, err := tufClient.Update(); err != nil {
125+
if err == tuf.ErrNoRootKeys {
126+
if err := tufClient.Init(tufconfig.RootKeys, 1); err != nil {
127+
return nil, fmt.Errorf("error initializing TUF root keys: %s", err)
128+
}
129+
if _, err := tufClient.Update(); err != nil && !tuf.IsLatestSnapshot(err) {
130+
return nil, fmt.Errorf("error updating TUF metadata after init: %s", err)
131+
}
132+
} else if !tuf.IsLatestSnapshot(err) {
133+
return nil, fmt.Errorf("error updating TUF metadata: %s", err)
134+
}
135+
}
120136
l.tufClient = tufClient
121137
}
122138
l.httpClient = &http.Client{Transport: &http.Transport{

‎pkg/postgres/migrate.go‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import (
44
"strconv"
55
"time"
66

7-
"github.com/jackc/pgx"
87
"github.com/inconshreveable/log15"
8+
"github.com/jackc/pgx"
99
)
1010

1111
type Step func(*DBTx) error
@@ -39,7 +39,9 @@ func (m Migrations) Migrate(db *DB) error {
3939
var initialized bool
4040
for _, migration := range m {
4141
if !initialized {
42-
db.Exec("CREATE TABLE IF NOT EXISTS schema_migrations (id bigint PRIMARY KEY)")
42+
if err := db.Exec("CREATE TABLE IF NOT EXISTS schema_migrations (id bigint PRIMARY KEY)"); err != nil {
43+
return err
44+
}
4345
initialized = true
4446
}
4547

0 commit comments

Comments
 (0)