From e566f9b3626780d6344f9c8ebe46558778fc841b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gra=C3=B1a?= Date: Wed, 18 Mar 2026 10:02:55 -0300 Subject: [PATCH 01/12] wip --- .github/workflows/checks.yml | 41 +++++++++++ .github/workflows/ci.yaml | 66 +++++++---------- .github/workflows/push.yml | 76 ------------------- .golangci.yml | 138 ++++++----------------------------- .pre-commit-config.yaml | 35 +++++---- 5 files changed, 112 insertions(+), 244 deletions(-) create mode 100644 .github/workflows/checks.yml delete mode 100644 .github/workflows/push.yml diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 00000000..ba51a774 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,41 @@ +name: "Checks" +on: + pull_request: + push: + branches: + - main + +jobs: + build: + name: "Build and test" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: "go.mod" + + - name: Run unit tests + run: go test -v ./... + + - name: Build binaries + run: go install ./cmd/... + + precommit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: "go.mod" + + - uses: j178/prek-action@v1 + with: + extra-args: --all-files --stage=manual + + - uses: golangci/golangci-lint-action@v9 + with: + version: latest + + - name: generate command strings + run: go generate ./... && git diff --exit-code diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 61d85d23..ae79e889 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,29 +9,21 @@ jobs: runs-on: ubuntu-latest steps: - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 with: version: v0.9.1 - - - name: Login to DockerHub - uses: docker/login-action@v3 + + - uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: "Fetch latest tag" - id: get-latest-tag + - id: get-latest-tag uses: "WyriHaximus/github-action-get-previous-tag@v2" - - - name: Build and push Postgres 15 + + - name: Build and push Postgres 15 id: docker_build_15 uses: docker/build-push-action@v6 with: @@ -45,8 +37,8 @@ jobs: tags: | flyio/postgres-flex:15 flyio/postgres-flex:15.10 - - - name: Build and push Postgres 15 Timescale DB + + - name: Build and push Postgres 15 Timescale DB id: docker_build_15_timescaledb uses: docker/build-push-action@v6 with: @@ -61,8 +53,7 @@ jobs: flyio/postgres-flex-timescaledb:15 flyio/postgres-flex-timescaledb:15.10 - - - name: Build and push Postgres 16 + - name: Build and push Postgres 16 id: docker_build_16 uses: docker/build-push-action@v6 with: @@ -76,8 +67,8 @@ jobs: tags: | flyio/postgres-flex:16 flyio/postgres-flex:16.6 - - - name: Build and push Postgres 16 Timescale DB + + - name: Build and push Postgres 16 Timescale DB id: docker_build_16_timescaledb uses: docker/build-push-action@v6 with: @@ -92,8 +83,7 @@ jobs: flyio/postgres-flex-timescaledb:16 flyio/postgres-flex-timescaledb:16.6 - - - name: Build and push Postgres 17 + - name: Build and push Postgres 17 id: docker_build_17 uses: docker/build-push-action@v6 with: @@ -107,8 +97,8 @@ jobs: tags: | flyio/postgres-flex:17 flyio/postgres-flex:17.2 - - - name: Build and push Postgres 17 Timescale DB + + - name: Build and push Postgres 17 Timescale DB id: docker_build_17_timescaledb uses: docker/build-push-action@v6 with: @@ -122,21 +112,21 @@ jobs: tags: | flyio/postgres-flex-timescaledb:17 flyio/postgres-flex-timescaledb:17.2 - - - name: Postgres 15 Image digest + + - name: Postgres 15 Image digest run: echo ${{ steps.docker_build_15.outputs.digest }} - - - name: Postgres 15 TimescaleDB Image digest + + - name: Postgres 15 TimescaleDB Image digest run: echo ${{ steps.docker_build_15_timescaledb.outputs.digest }} - - - name: Postgres 16 Image digest + + - name: Postgres 16 Image digest run: echo ${{ steps.docker_build_16.outputs.digest }} - - - name: Postgres 16 TimescaleDB Image digest + + - name: Postgres 16 TimescaleDB Image digest run: echo ${{ steps.docker_build_16_timescaledb.outputs.digest }} - - - name: Postgres 17 Image digest + + - name: Postgres 17 Image digest run: echo ${{ steps.docker_build_17.outputs.digest }} - - - name: Postgres 17 TimescaleDB Image digest + + - name: Postgres 17 TimescaleDB Image digest run: echo ${{ steps.docker_build_17_timescaledb.outputs.digest }} diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml deleted file mode 100644 index 6c9e447b..00000000 --- a/.github/workflows/push.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: "Push" -on: - push: - branches: - - main - pull_request: - types: - - opened - - synchronize - - reopened - -jobs: - build: - name: "Build" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-go@v6 - with: - go-version: '1.23' - go-version-file: 'go.mod' - cache: true - - - name: Build binaries - run: go install ./cmd/... - - unit: - name: "Unit Tests" - runs-on: ubuntu-latest - strategy: - fail-fast: false - - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-go@v6 - with: - go-version: '1.23' - go-version-file: 'go.mod' - cache: true - - - name: Run unit tests - run: go test -v ./... - - - staticcheck: - name: "Staticcheck" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-go@v6 - with: - go-version: '1.23' - go-version-file: 'go.mod' - cache: true - - - uses: dominikh/staticcheck-action@v1.4.0 - with: - install-go: false - - errcheck: - name: "Errcheck" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-go@v6 - with: - go-version: '1.23' - go-version-file: 'go.mod' - cache: true - - - run: go install github.com/kisielk/errcheck@latest - - run: errcheck -ignore 'AddCheck' ./... diff --git a/.golangci.yml b/.golangci.yml index 95902480..98f7af0d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,125 +1,29 @@ -issues: - # List of regexps of issue texts to exclude, empty list by default. - # But independently from this option we use default exclude patterns, - # it can be disabled by `exclude-use-default: false`. To list all - # excluded by default patterns execute `golangci-lint run --help` - - exclude-rules: - # Exclude gosimple bool check - - linters: - - gosimple - text: "S(1002|1008|1021)" - # Exclude failing staticchecks for now - - linters: - - staticcheck - text: "SA(1006|1019|4006|4010|4017|5007|6005|9004):" - # Exclude lll issues for long lines with go:generate - - linters: - - lll - source: "^//go:generate " - - # Maximum issues count per one linter. Set to 0 to disable. Default is 50. - max-issues-per-linter: 0 - - # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. - max-same-issues: 0 +version: "2" +run: + issues-exit-code: 1 + tests: true linters: - disable-all: true enable: - # - gofumpt - # - goimports - - gosimple - govet - ineffassign - staticcheck - unconvert - unused - fast: true - -# options for analysis running -run: - go: "1.19" - - # default concurrency is a available CPU number - concurrency: 4 - - # timeout for analysis, e.g. 30s, 5m, default is 1m - timeout: 10m - - # exit code when at least one issue was found, default is 1 - issues-exit-code: 1 - - # include test files or not, default is true - tests: true - - # list of build tags, all linters use it. Default is empty list. - #build-tags: - # - mytag - - # which dirs to skip: issues from them won't be reported; - # can use regexp here: generated.*, regexp is applied on full path; - # default value is empty list, but default dirs are skipped independently - # from this option's value (see skip-dirs-use-default). - #skip-dirs: - # - src/external_libs - # - autogenerated_by_my_lib - - # default is true. Enables skipping of directories: - # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ - skip-dirs-use-default: true - - # which files to skip: they will be analyzed, but issues from them - # won't be reported. Default value is empty list, but there is - # no need to include all autogenerated files, we confidently recognize - # autogenerated files. If it's not please let us know. - skip-files: - - ".*\\.hcl2spec\\.go$" - - "docstrings/gen.go" - # - lib/bad.go - - # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": - # If invoked with -mod=readonly, the go command is disallowed from the implicit - # automatic updating of go.mod described above. Instead, it fails when any changes - # to go.mod are needed. This setting is most useful to check that go.mod does - # not need updates, such as in a continuous integration and testing system. - # If invoked with -mod=vendor, the go command assumes that the vendor - # directory holds the correct copies of dependencies and ignores - # the dependency descriptions in go.mod. - # modules-download-mode: vendor - -# output configuration options -output: - # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" - format: colored-line-number - - # print lines of code with issue, default is true - print-issued-lines: true - - # print linter name in the end of issue text, default is true - print-linter-name: true - - # make issues output unique by line, default is true - uniq-by-line: true - -# all available settings of specific linters -linters-settings: - gofumpt: - module-path: github.com/fly-apps/postgres-flex - errcheck: - # report about not checking of errors in type assetions: `a := b.(MyStruct)`; - # default is false: such cases aren't reported by default. - check-type-assertions: false - - # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; - # default is false: such cases aren't reported by default. - check-blank: false - - # [deprecated] comma-separated list of pairs of the form pkg:regex - # the regex is used to ignore names within pkg. (default "fmt:.*"). - # see https://github.com/kisielk/errcheck#the-deprecated-method for details - ignore: fmt:.*,io/ioutil:^Read.*,io:Close - - # path to a file containing a list of functions to exclude from checking - # see https://github.com/kisielk/errcheck#excluding-functions for details - #exclude: /path/to/file.txt + - nlreturn + - bodyclose + - gocheckcompilerdirectives + - errname + - errchkjson + settings: + nlreturn: + block-size: 3 + + staticcheck: + checks: + - all + - -ST1003 # struct field ... should be ... (mostly acronyms such as Http -> HTTP) + +formatters: + enable: + - gofumpt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f6d2d9d9..2e5894e0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,27 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files -- repo: https://github.com/golangci/golangci-lint - rev: v1.50.0 + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + + - repo: https://github.com/tekwizely/pre-commit-golang + rev: v1.0.0-rc.4 hooks: - - id: golangci-lint - # pre-commit github action runs as "manual" hook - # because golangci-lint's action is more useful than - # the pre-commit hook in that case - stages: [commit] + - id: go-mod-tidy + + # NOTE: This pre-commit hook is ignored when running on Github Workflow + # because goalngci-lint github action is much more useful than the pre-commit action. + # The trick is to run github action only for "manual" hook stage + - repo: https://github.com/golangci/golangci-lint + rev: v2.11.3 + hooks: + - id: golangci-lint + stages: [pre-commit] + args: + - --max-issues-per-linter=0 + - --max-same-issues=0 From c5c30e260d0fcd314e2f028949565003339f1954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gra=C3=B1a?= Date: Wed, 18 Mar 2026 10:04:09 -0300 Subject: [PATCH 02/12] golangli-lint fixes --- cmd/event_handler/main.go | 5 +---- cmd/flexctl/backups.go | 10 +++++++--- cmd/flexctl/main.go | 4 ++-- cmd/monitor/monitor_backup_retention.go | 1 - cmd/monitor/monitor_backup_schedule.go | 1 + cmd/monitor/monitor_backup_schedule_test.go | 4 ++-- cmd/monitor/monitor_cluster_state.go | 1 + cmd/monitor/monitor_replication_slots.go | 1 + cmd/pg_unregister/main.go | 1 + cmd/start/main.go | 3 +++ internal/api/handle_event.go | 1 + internal/api/handle_users.go | 1 - internal/api/response.go | 4 ++-- internal/flybarman/node.go | 9 +++++---- internal/flybarman/node_test.go | 2 +- internal/flycheck/role.go | 1 + internal/flycheck/vm.go | 9 ++++----- internal/flypg/admin/admin.go | 8 ++++++++ internal/flypg/barman_config.go | 3 ++- internal/flypg/barman_config_test.go | 3 --- internal/flypg/barman_restore.go | 3 ++- internal/flypg/barman_restore_test.go | 4 ---- internal/flypg/barman_test.go | 1 - internal/flypg/pg.go | 7 ++++--- internal/flypg/pg_test.go | 8 +++----- internal/flypg/readonly.go | 2 +- internal/flypg/registration.go | 3 ++- internal/flypg/repmgr.go | 6 ++++-- internal/flypg/restore.go | 8 ++++---- internal/flypg/s3_credentials.go | 8 ++++---- internal/flypg/s3_credentials_test.go | 1 - internal/flypg/ssh.go | 6 +++--- internal/flypg/zombie.go | 4 +++- internal/flypg/zombie_test.go | 3 --- internal/privnet/sixpn.go | 5 ++++- internal/supervisor/output.go | 1 + internal/utils/shell.go | 1 + 37 files changed, 79 insertions(+), 64 deletions(-) diff --git a/cmd/event_handler/main.go b/cmd/event_handler/main.go index aa5cfdba..6a4351a5 100644 --- a/cmd/event_handler/main.go +++ b/cmd/event_handler/main.go @@ -19,10 +19,7 @@ func main() { details := flag.String("details", "", "details") flag.Parse() - succ := true - if *success == "0" { - succ = false - } + succ := *success != "0" req := api.EventRequest{ Name: *event, diff --git a/cmd/flexctl/backups.go b/cmd/flexctl/backups.go index 346f4976..7e825f39 100644 --- a/cmd/flexctl/backups.go +++ b/cmd/flexctl/backups.go @@ -57,6 +57,7 @@ var backupShowCmd = &cobra.Command{ if !backupsEnabled() { return fmt.Errorf("backups are not enabled") } + return showBackup(cmd, args) }, Args: cobra.ExactArgs(1), @@ -172,6 +173,7 @@ func listBackups(cmd *cobra.Command) error { } fmt.Println(string(jsonBytes)) + return nil } @@ -229,7 +231,7 @@ func backupsEnabled() bool { } func newBackupConfig() *cobra.Command { - var cmd = &cobra.Command{ + cmd := &cobra.Command{ Use: "config", Short: "Manage backup configuration", } @@ -248,6 +250,7 @@ func getAppName() (string, error) { if name == "" { return "", fmt.Errorf("FLY_APP_NAME is not set") } + return name, nil } @@ -257,11 +260,12 @@ func getApiUrl() (string, error) { return "", err } url := fmt.Sprintf("http://%s.internal:5500", hostname) + return url, nil } func newConfigShow() *cobra.Command { - var cmd = &cobra.Command{ + cmd := &cobra.Command{ Use: "show", Short: "Show current configuration", RunE: func(cmd *cobra.Command, args []string) error { @@ -308,7 +312,7 @@ type configUpdateResult struct { } func newConfigUpdate() *cobra.Command { - var cmd = &cobra.Command{ + cmd := &cobra.Command{ Use: "update", Short: "Update configuration", } diff --git a/cmd/flexctl/main.go b/cmd/flexctl/main.go index a1574dc3..1547737c 100644 --- a/cmd/flexctl/main.go +++ b/cmd/flexctl/main.go @@ -8,14 +8,14 @@ import ( ) func main() { - var rootCmd = &cobra.Command{ + rootCmd := &cobra.Command{ Use: "flexctl", SilenceErrors: true, SilenceUsage: true, } // Backup commands - var backupCmd = &cobra.Command{Use: "backup"} + backupCmd := &cobra.Command{Use: "backup"} backupCmd.Aliases = []string{"backups"} rootCmd.AddCommand(backupCmd) diff --git a/cmd/monitor/monitor_backup_retention.go b/cmd/monitor/monitor_backup_retention.go index f53dd34f..441fa53c 100644 --- a/cmd/monitor/monitor_backup_retention.go +++ b/cmd/monitor/monitor_backup_retention.go @@ -9,7 +9,6 @@ import ( ) func monitorBackupRetention(ctx context.Context, node *flypg.Node, barman *flypg.Barman) { - ticker := time.NewTicker(defaultBackupRetentionEvalFrequency) defer ticker.Stop() diff --git a/cmd/monitor/monitor_backup_schedule.go b/cmd/monitor/monitor_backup_schedule.go index fe9d1262..91b3eb08 100644 --- a/cmd/monitor/monitor_backup_schedule.go +++ b/cmd/monitor/monitor_backup_schedule.go @@ -111,6 +111,7 @@ func calculateNextBackupTime(barman *flypg.Barman, lastBackupTime time.Time) tim if lastBackupTime.IsZero() { return -1 } + return time.Until(lastBackupTime.Add(backupFrequency(barman))) } diff --git a/cmd/monitor/monitor_backup_schedule_test.go b/cmd/monitor/monitor_backup_schedule_test.go index c303fceb..75943e8a 100644 --- a/cmd/monitor/monitor_backup_schedule_test.go +++ b/cmd/monitor/monitor_backup_schedule_test.go @@ -115,7 +115,6 @@ func TestCalculateNextBackupTime(t *testing.T) { t.Fatalf("expected next backup time duration to be %f, but got %f", expected, val) } }) - } func setDefaultEnv(t *testing.T) { @@ -129,13 +128,14 @@ func setup(t *testing.T) error { if _, err := os.Stat(pgTestDirectory); err != nil { if os.IsNotExist(err) { - if err := os.Mkdir(pgTestDirectory, 0750); err != nil { + if err := os.Mkdir(pgTestDirectory, 0o750); err != nil { return err } } else { return err } } + return nil } diff --git a/cmd/monitor/monitor_cluster_state.go b/cmd/monitor/monitor_cluster_state.go index aac3d225..da7ce8b8 100644 --- a/cmd/monitor/monitor_cluster_state.go +++ b/cmd/monitor/monitor_cluster_state.go @@ -42,6 +42,7 @@ func clusterStateMonitorTick(ctx context.Context, node *flypg.Node) error { if err := flypg.Quarantine(ctx, node, primary); err != nil { return fmt.Errorf("failed to quarantine failed primary: %s", err) } + return fmt.Errorf("primary has been quarantined: %s", err) } else if err != nil { return fmt.Errorf("failed to run zombie diagnosis: %s", err) diff --git a/cmd/monitor/monitor_replication_slots.go b/cmd/monitor/monitor_replication_slots.go index 42ed7553..52d0c6f8 100644 --- a/cmd/monitor/monitor_replication_slots.go +++ b/cmd/monitor/monitor_replication_slots.go @@ -74,6 +74,7 @@ func replicationSlotMonitorTick(ctx context.Context, node *flypg.Node, inactiveS } delete(inactiveSlotStatus, int(slot.MemberID)) + continue } diff --git a/cmd/pg_unregister/main.go b/cmd/pg_unregister/main.go index de7c5448..6624a834 100644 --- a/cmd/pg_unregister/main.go +++ b/cmd/pg_unregister/main.go @@ -107,6 +107,7 @@ func removeReplicationSlot(ctx context.Context, conn *pgx.Conn, slotName string) if err == pgx.ErrNoRows { return nil } + return fmt.Errorf("failed to get replication slot %s: %v", slotName, err) } diff --git a/cmd/start/main.go b/cmd/start/main.go index b3cb3fbf..70052004 100644 --- a/cmd/start/main.go +++ b/cmd/start/main.go @@ -76,6 +76,7 @@ func main() { fmt.Printf("failed post-init: %s. Retrying...\n", err) continue } + return } }() @@ -159,6 +160,7 @@ func scaleToZeroWorker(ctx context.Context, node *flypg.Node) error { if current > 1 { continue } + return fmt.Errorf("scale to zero condition hit") } } @@ -176,6 +178,7 @@ func getCurrentConnCount(ctx context.Context, node *flypg.Node) (int, error) { if err := conn.QueryRow(ctx, sql).Scan(¤t); err != nil { return 0, err } + return current, nil } diff --git a/internal/api/handle_event.go b/internal/api/handle_event.go index c46ab71a..a28a9250 100644 --- a/internal/api/handle_event.go +++ b/internal/api/handle_event.go @@ -37,6 +37,7 @@ func handleEvent(w http.ResponseWriter, r *http.Request) { errMsg := fmt.Sprintf("[ERROR] Event %q failed: %s", event.Name, event.Details) log.Println(errMsg) renderErr(w, errors.New(errMsg)) + return } diff --git a/internal/api/handle_users.go b/internal/api/handle_users.go index 41a7d01c..4dce1959 100644 --- a/internal/api/handle_users.go +++ b/internal/api/handle_users.go @@ -29,7 +29,6 @@ func handleListUsers(w http.ResponseWriter, r *http.Request) { } renderJSON(w, res, http.StatusOK) - } func handleGetUser(w http.ResponseWriter, r *http.Request) { diff --git a/internal/api/response.go b/internal/api/response.go index 89aba70a..9c11b061 100644 --- a/internal/api/response.go +++ b/internal/api/response.go @@ -3,9 +3,8 @@ package api import ( "encoding/json" "errors" - "net/http" - "log" + "net/http" "github.com/jackc/pgconn" "github.com/jackc/pgx/v5" @@ -48,5 +47,6 @@ func status(err error) int { return http.StatusInternalServerError } } + return http.StatusInternalServerError } diff --git a/internal/flybarman/node.go b/internal/flybarman/node.go index 0a53796a..09e517bb 100644 --- a/internal/flybarman/node.go +++ b/internal/flybarman/node.go @@ -107,7 +107,7 @@ retention_policy = RECOVERY WINDOW OF 7 days wal_retention_policy = main `, n.AppName, n.AppName) - if err := os.WriteFile(n.BarmanConfigFile, []byte(barmanConfigFileContent), 0644); err != nil { + if err := os.WriteFile(n.BarmanConfigFile, []byte(barmanConfigFileContent), 0o644); err != nil { return fmt.Errorf("failed write %s: %s", n.BarmanConfigFile, err) } @@ -131,18 +131,18 @@ wal_retention_policy = main log.Println("Barman home directory successfully.") passStr := fmt.Sprintf("*:*:*:%s:%s", n.ReplCredentials.Username, n.ReplCredentials.Password) - if err := os.WriteFile(n.PasswordConfigPath, []byte(passStr), 0700); err != nil { + if err := os.WriteFile(n.PasswordConfigPath, []byte(passStr), 0o700); err != nil { return fmt.Errorf("failed to write file %s: %s", n.PasswordConfigPath, err) } // We need this in case the user ssh to the vm as root - if err := os.WriteFile(n.RootPasswordConfigPath, []byte(passStr), 0700); err != nil { + if err := os.WriteFile(n.RootPasswordConfigPath, []byte(passStr), 0o700); err != nil { return fmt.Errorf("failed to write file %s: %s", n.RootPasswordConfigPath, err) } barmanCronFileContent := `* * * * * /usr/local/bin/barman_cron ` - if err := os.WriteFile(n.BarmanCronFile, []byte(barmanCronFileContent), 0644); err != nil { + if err := os.WriteFile(n.BarmanCronFile, []byte(barmanCronFileContent), 0o644); err != nil { return fmt.Errorf("failed write %s: %s", n.BarmanCronFile, err) } log.Println(n.BarmanCronFile + " created successfully.") @@ -195,5 +195,6 @@ func (n *Node) deleteGlobalBarmanFile() error { } log.Println(n.GlobalBarmanConfigFile + " deleted successfully") + return nil } diff --git a/internal/flybarman/node_test.go b/internal/flybarman/node_test.go index 2bf57274..8e2ac0bc 100644 --- a/internal/flybarman/node_test.go +++ b/internal/flybarman/node_test.go @@ -108,7 +108,7 @@ func setup(t *testing.T) error { if _, err := os.Stat(barmanTestDirectory); err != nil { if os.IsNotExist(err) { - if err := os.Mkdir(barmanTestDirectory, 0750); err != nil { + if err := os.Mkdir(barmanTestDirectory, 0o750); err != nil { return err } } else { diff --git a/internal/flycheck/role.go b/internal/flycheck/role.go index 6e01f0ce..0bee5d1e 100644 --- a/internal/flycheck/role.go +++ b/internal/flycheck/role.go @@ -61,5 +61,6 @@ func PostgreSQLRole(ctx context.Context, checks *check.CheckSuite) (*check.Check return "unknown", nil } }) + return checks, nil } diff --git a/internal/flycheck/vm.go b/internal/flycheck/vm.go index cdac0fb5..98862e5e 100644 --- a/internal/flycheck/vm.go +++ b/internal/flycheck/vm.go @@ -15,7 +15,6 @@ import ( // CheckVM for system / disk checks func CheckVM(checks *check.CheckSuite) *check.CheckSuite { - checks.AddCheck("checkDisk", func() (string, error) { return checkDisk("/data/") }) @@ -83,7 +82,6 @@ func checkLoad() (string, error) { var loadAverage1, loadAverage5, loadAverage10 float64 var runningProcesses, totalProcesses, lastProcessID int raw, err := os.ReadFile("/proc/loadavg") - if err != nil { return "", err } @@ -114,7 +112,6 @@ func checkDisk(dir string) (string, error) { var stat syscall.Statfs_t err := syscall.Statfs(dir, &stat) - if err != nil { return "", fmt.Errorf("%s: %s", dir, err) } @@ -136,15 +133,14 @@ func diskUsage(dir string) (size uint64, available uint64, err error) { var stat syscall.Statfs_t err = syscall.Statfs(dir, &stat) - if err != nil { return 0, 0, fmt.Errorf("%s: %s", dir, err) } size = stat.Blocks * uint64(stat.Bsize) available = stat.Bavail * uint64(stat.Bsize) - return size, available, nil + return size, available, nil } func round(val float64, roundOn float64, places int) (newVal float64) { @@ -158,8 +154,10 @@ func round(val float64, roundOn float64, places int) (newVal float64) { round = math.Floor(digit) } newVal = round / pow + return } + func dataSize(size uint64) string { var suffixes [5]string suffixes[0] = "B" @@ -171,6 +169,7 @@ func dataSize(size uint64) string { base := math.Log(float64(size)) / math.Log(1024) getSize := round(math.Pow(1024, base-math.Floor(base)), .5, 2) getSuffix := suffixes[int(math.Floor(base))] + return fmt.Sprint(strconv.FormatFloat(getSize, 'f', -1, 64) + " " + getSuffix) } diff --git a/internal/flypg/admin/admin.go b/internal/flypg/admin/admin.go index 985dd483..fdd8bf94 100644 --- a/internal/flypg/admin/admin.go +++ b/internal/flypg/admin/admin.go @@ -24,6 +24,7 @@ func GrantSuperuser(ctx context.Context, pg *pgx.Conn, username string) error { sql := fmt.Sprintf("ALTER USER %s WITH SUPERUSER;", username) _, err := pg.Exec(ctx, sql) + return err } @@ -69,6 +70,7 @@ func ChangePassword(ctx context.Context, pg *pgx.Conn, username, password string sql := fmt.Sprintf("ALTER USER %s WITH LOGIN PASSWORD '%s';", username, password) _, err := pg.Exec(ctx, sql) + return err } @@ -100,6 +102,7 @@ func CreateDatabase(ctx context.Context, pg *pgx.Conn, name string) error { sql := fmt.Sprintf("CREATE DATABASE %s;", name) _, err = pg.Exec(ctx, sql) + return err } @@ -336,6 +339,7 @@ func ReloadPostgresConfig(ctx context.Context, pg *pgx.Conn) error { sql := "SELECT pg_reload_conf()" _, err := pg.Exec(ctx, sql) + return err } @@ -345,6 +349,7 @@ func SettingExists(ctx context.Context, pg *pgx.Conn, setting string) (bool, err if err := pg.QueryRow(ctx, sql).Scan(&out); err != nil { return false, err } + return out, nil } @@ -354,6 +359,7 @@ func ExtensionAvailable(ctx context.Context, pg *pgx.Conn, extension string) (bo if err := pg.QueryRow(ctx, sql).Scan(&out); err != nil { return false, err } + return out, nil } @@ -364,6 +370,7 @@ func SettingRequiresRestart(ctx context.Context, pg *pgx.Conn, setting string) ( if err := row.Scan(&out); err != nil { return false, err } + return out, nil } @@ -388,6 +395,7 @@ func GetSetting(ctx context.Context, pg *pgx.Conn, setting string) (*PGSetting, if err := row.Scan(&out.Name, &out.Setting, &out.VarType, &out.MinVal, &out.MaxVal, &out.EnumVals, &out.Context, &out.Unit, &out.Desc, &out.PendingRestart); err != nil { return nil, err } + return &out, nil } diff --git a/internal/flypg/barman_config.go b/internal/flypg/barman_config.go index 1bf230d9..68df7f76 100644 --- a/internal/flypg/barman_config.go +++ b/internal/flypg/barman_config.go @@ -170,7 +170,7 @@ func (c *BarmanConfig) Validate(requestedChanges map[string]interface{}) error { func (c *BarmanConfig) initialize(store *state.Store, configDir string) error { // Ensure directory exists - if err := os.MkdirAll(configDir, 0600); err != nil { + if err := os.MkdirAll(configDir, 0o600); err != nil { return fmt.Errorf("failed to create barman config directory: %s", err) } @@ -215,6 +215,7 @@ func convertRecoveryWindowDuration(durationStr string) string { return strings.TrimSuffix(durationStr, unit) + " " + text } } + return durationStr } diff --git a/internal/flypg/barman_config_test.go b/internal/flypg/barman_config_test.go index 8e585563..2b913dc5 100644 --- a/internal/flypg/barman_config_test.go +++ b/internal/flypg/barman_config_test.go @@ -20,7 +20,6 @@ func TestValidateBarmanConfig(t *testing.T) { } t.Run("valid-config", func(t *testing.T) { - conf := ConfigMap{ "archive_timeout": "120s", "recovery_window": "7d", @@ -151,7 +150,6 @@ func TestValidateBarmanConfig(t *testing.T) { t.Fatalf("expected error, got nil") } }) - } func TestBarmanConfigSettings(t *testing.T) { @@ -183,7 +181,6 @@ func TestBarmanConfigSettings(t *testing.T) { if b.Settings.ArchiveTimeout != "60s" { t.Fatalf("expected archive_timeout to be 60s, but got %s", b.Settings.ArchiveTimeout) } - }) } diff --git a/internal/flypg/barman_restore.go b/internal/flypg/barman_restore.go index 28961dd7..cb999083 100644 --- a/internal/flypg/barman_restore.go +++ b/internal/flypg/barman_restore.go @@ -83,6 +83,7 @@ func (*BarmanRestore) walReplayAndReset(ctx context.Context, node *Node) error { if os.IsNotExist(err) { return nil } + return fmt.Errorf("failed backing up pg_hba.conf: %s", err) } @@ -185,7 +186,7 @@ func (b *BarmanRestore) restoreFromBackup(ctx context.Context) error { } // Write the recovery.signal file - if err := os.WriteFile("/data/postgresql/recovery.signal", []byte(""), 0600); err != nil { + if err := os.WriteFile("/data/postgresql/recovery.signal", []byte(""), 0o600); err != nil { return fmt.Errorf("failed to write recovery.signal: %s", err) } diff --git a/internal/flypg/barman_restore_test.go b/internal/flypg/barman_restore_test.go index 884724a9..11d16380 100644 --- a/internal/flypg/barman_restore_test.go +++ b/internal/flypg/barman_restore_test.go @@ -170,7 +170,6 @@ func TestNewBarmanRestore(t *testing.T) { if restore.endpoint != "https://fly.storage.tigris.dev" { t.Fatalf("expected endpoint to be https://fly.storage.tigris.dev, got %s", restore.endpoint) } - }) t.Run("target", func(t *testing.T) { @@ -244,7 +243,6 @@ func TestNewBarmanRestore(t *testing.T) { if restore.recoveryTargetTimeline != "2" { t.Fatalf("expected recovery target timeline to be 2, got %s", restore.recoveryTargetTimeline) } - }) } @@ -311,7 +309,6 @@ func TestParseBackups(t *testing.T) { if backup.Name != "" { t.Fatalf("expected name to be empty, but got %s", backup.Name) } - }) t.Run("second-backup", func(t *testing.T) { @@ -339,7 +336,6 @@ func TestParseBackups(t *testing.T) { if backup.EndTime != "Wed Jun 26 17:27:02 2024" { t.Fatalf("expected end time to be Wed Jun 26 17:27:02 2024, got %s", backup.EndTime) } - }) }) } diff --git a/internal/flypg/barman_test.go b/internal/flypg/barman_test.go index 7d2c019b..152899a0 100644 --- a/internal/flypg/barman_test.go +++ b/internal/flypg/barman_test.go @@ -105,5 +105,4 @@ func TestFormatTimestamp(t *testing.T) { func setDefaultEnv(t *testing.T) { t.Setenv("S3_ARCHIVE_CONFIG", "https://my-key:my-secret@fly.storage.tigris.dev/my-bucket/my-directory") t.Setenv("FLY_APP_NAME", "postgres-flex") - } diff --git a/internal/flypg/pg.go b/internal/flypg/pg.go index 94a294ab..07419a89 100644 --- a/internal/flypg/pg.go +++ b/internal/flypg/pg.go @@ -314,7 +314,7 @@ func (c *PGConfig) initialize(store *state.Store) error { } func (c *PGConfig) writePGConfigEntries(entries []string) error { - file, err := os.OpenFile(c.ConfigFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) + file, err := os.OpenFile(c.ConfigFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600) if err != nil { return err } @@ -330,7 +330,7 @@ func (c *PGConfig) writePGConfigEntries(entries []string) error { } func (c *PGConfig) writePasswordFile(pwd string) error { - if err := os.WriteFile(c.passwordFilePath, []byte(pwd), 0600); err != nil { + if err := os.WriteFile(c.passwordFilePath, []byte(pwd), 0o600); err != nil { return fmt.Errorf("failed to write default password to %s: %s", c.passwordFilePath, err) } @@ -566,7 +566,7 @@ func (c *PGConfig) setDefaultHBA() error { } path := fmt.Sprintf("%s/pg_hba.conf", c.DataDir) - file, err := os.OpenFile(path, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0600) + file, err := os.OpenFile(path, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0o600) if err != nil { return fmt.Errorf("failed to create pg_hba.conf file: %s", err) } @@ -604,5 +604,6 @@ func diskSizeInBytes(dir string) (uint64, error) { if err := syscall.Statfs(dir, &stat); err != nil { return 0, err } + return stat.Blocks * uint64(stat.Bsize), nil } diff --git a/internal/flypg/pg_test.go b/internal/flypg/pg_test.go index a5efe7f3..006daa79 100644 --- a/internal/flypg/pg_test.go +++ b/internal/flypg/pg_test.go @@ -153,7 +153,6 @@ func TestPGConfigInitialization(t *testing.T) { }) t.Run("custom-archive-timeout-with-m", func(t *testing.T) { - barman.SetUserConfig(ConfigMap{"archive_timeout": "60m"}) if err := writeUserConfigFile(barman); err != nil { @@ -498,7 +497,7 @@ func setup(t *testing.T) error { if _, err := os.Stat(pgTestDirectory); err != nil { if os.IsNotExist(err) { - if err := os.Mkdir(pgTestDirectory, 0750); err != nil { + if err := os.Mkdir(pgTestDirectory, 0o750); err != nil { return err } } else { @@ -508,7 +507,7 @@ func setup(t *testing.T) error { if _, err := os.Stat(testBarmanConfigDir); err != nil { if os.IsNotExist(err) { - if err := os.Mkdir(testBarmanConfigDir, 0750); err != nil { + if err := os.Mkdir(testBarmanConfigDir, 0o750); err != nil { return err } } else { @@ -592,7 +591,6 @@ func TestValidateCompatibility(t *testing.T) { if _, err := pgConf.validateCompatibility(valid); err == nil { t.Fatal("expected validation to fail when repmgr is missing") } - }) t.Run("WalLevel", func(t *testing.T) { @@ -625,7 +623,6 @@ func TestValidateCompatibility(t *testing.T) { if _, err := pgConf.validateCompatibility(invalid); err == nil { t.Fatal(err) } - }) t.Run("WalLevelMinimal", func(t *testing.T) { @@ -740,6 +737,7 @@ func stubPGConfigFile() error { return err } defer func() { _ = file.Close() }() + return file.Sync() } diff --git a/internal/flypg/readonly.go b/internal/flypg/readonly.go index 1c075538..42b727f0 100644 --- a/internal/flypg/readonly.go +++ b/internal/flypg/readonly.go @@ -115,7 +115,7 @@ func writeReadOnlyLock() error { return nil } - if err := os.WriteFile(readOnlyLockFile, []byte(time.Now().String()), 0600); err != nil { + if err := os.WriteFile(readOnlyLockFile, []byte(time.Now().String()), 0o600); err != nil { return fmt.Errorf("failed to create readonly.lock: %s", err) } diff --git a/internal/flypg/registration.go b/internal/flypg/registration.go index 6e8af8be..dc6fe247 100644 --- a/internal/flypg/registration.go +++ b/internal/flypg/registration.go @@ -64,7 +64,7 @@ func isRegistered(ctx context.Context, conn *pgx.Conn, n *Node) (bool, error) { } func issueRegistrationCert() error { - return os.WriteFile(registrationFile, []byte(""), 0600) + return os.WriteFile(registrationFile, []byte(""), 0o600) } func registrationCertExists() bool { @@ -73,6 +73,7 @@ func registrationCertExists() bool { return false } } + return true } diff --git a/internal/flypg/repmgr.go b/internal/flypg/repmgr.go index b7058a46..4714d275 100644 --- a/internal/flypg/repmgr.go +++ b/internal/flypg/repmgr.go @@ -113,7 +113,7 @@ func (r *RepMgr) initialize() error { } entriesStr := strings.Join(entries, "") - if err := os.WriteFile(r.ConfigPath, []byte(entriesStr), 0600); err != nil { + if err := os.WriteFile(r.ConfigPath, []byte(entriesStr), 0o600); err != nil { return fmt.Errorf("failed to create %s: %s", r.ConfigPath, err) } @@ -123,7 +123,7 @@ func (r *RepMgr) initialize() error { // Create password file that repmgr will hook into for internal operations. passStr := fmt.Sprintf("*:*:*:%s:%s", r.Credentials.Username, r.Credentials.Password) - if err := os.WriteFile(r.PasswordConfigPath, []byte(passStr), 0600); err != nil { + if err := os.WriteFile(r.PasswordConfigPath, []byte(passStr), 0o600); err != nil { return fmt.Errorf("failed to write file %s: %s", r.PasswordConfigPath, err) } @@ -335,6 +335,7 @@ func (r *RepMgr) regenReplicationConf(ctx context.Context) error { "standby", "clone", "-F"); err != nil { return fmt.Errorf("failed to regenerate replication conf: %s", err) } + return nil } @@ -528,6 +529,7 @@ func (r *RepMgr) InRegionPeerMachines(ctx context.Context) ([]string, error) { machineIDs = append(machineIDs, machine.Id) } } + return machineIDs, nil } diff --git a/internal/flypg/restore.go b/internal/flypg/restore.go index c21fc0f9..61344397 100644 --- a/internal/flypg/restore.go +++ b/internal/flypg/restore.go @@ -153,11 +153,11 @@ func backupHBAFile() error { return err } - return os.WriteFile(pathToHBABackup, val, 0600) + return os.WriteFile(pathToHBABackup, val, 0o600) } func grantLocalAccess() error { - file, err := os.OpenFile(pathToHBAFile, os.O_RDWR|os.O_TRUNC, 0600) + file, err := os.OpenFile(pathToHBAFile, os.O_RDWR|os.O_TRUNC, 0o600) if err != nil { return err } @@ -180,7 +180,7 @@ func restoreHBAFile() error { } // open the main pg_hba - file, err := os.OpenFile(pathToHBAFile, os.O_RDWR|os.O_TRUNC, 0600) + file, err := os.OpenFile(pathToHBAFile, os.O_RDWR|os.O_TRUNC, 0o600) if err != nil { return err } @@ -200,7 +200,7 @@ func restoreHBAFile() error { } func setRestoreLock() error { - file, err := os.OpenFile(restoreLockFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + file, err := os.OpenFile(restoreLockFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) if err != nil { return err } diff --git a/internal/flypg/s3_credentials.go b/internal/flypg/s3_credentials.go index 3b65da24..68727a93 100644 --- a/internal/flypg/s3_credentials.go +++ b/internal/flypg/s3_credentials.go @@ -45,7 +45,7 @@ func writeS3Credentials(ctx context.Context, s3AuthDir string) error { s3AuthFilePath := filepath.Join(s3AuthDir, s3AuthFileName) // Ensure the directory exists - if err := os.MkdirAll(s3AuthDir, 0700); err != nil { + if err := os.MkdirAll(s3AuthDir, 0o700); err != nil { return fmt.Errorf("failed to create AWS credentials directory: %w", err) } @@ -55,7 +55,7 @@ func writeS3Credentials(ctx context.Context, s3AuthDir string) error { } // Set file permissions - if err := os.Chmod(s3AuthFilePath, 0644); err != nil { + if err := os.Chmod(s3AuthFilePath, 0o644); err != nil { return fmt.Errorf("failed to set file permissions: %w", err) } @@ -76,8 +76,8 @@ func writeCredentialsToFile(credentials []*s3Credentials, pathToCredentialFile s // Write the credentials to disk for _, cred := range credentials { - _, err := file.WriteString(fmt.Sprintf("[%s]\naws_access_key_id=%s\naws_secret_access_key=%s\n\n", - cred.profile, cred.accessKeyID, cred.secretAccessKey)) + _, err := fmt.Fprintf(file, "[%s]\naws_access_key_id=%s\naws_secret_access_key=%s\n\n", + cred.profile, cred.accessKeyID, cred.secretAccessKey) if err != nil { return fmt.Errorf("failed to write s3 profile %s: %w", cred.profile, err) } diff --git a/internal/flypg/s3_credentials_test.go b/internal/flypg/s3_credentials_test.go index 68ea5f7e..7cf519b6 100644 --- a/internal/flypg/s3_credentials_test.go +++ b/internal/flypg/s3_credentials_test.go @@ -90,5 +90,4 @@ func TestWriteAWSCredentials(t *testing.T) { t.Fatalf("expected contents to be %s, but got %s", expected, string(contents)) } }) - } diff --git a/internal/flypg/ssh.go b/internal/flypg/ssh.go index 2f2340a0..fa2c4bbb 100644 --- a/internal/flypg/ssh.go +++ b/internal/flypg/ssh.go @@ -13,7 +13,7 @@ const ( ) func WriteSSHKey() error { - err := os.Mkdir("/data/.ssh", 0700) + err := os.Mkdir("/data/.ssh", 0o700) if err != nil && !os.IsExist(err) { return err } @@ -30,11 +30,11 @@ func WriteSSHKey() error { return fmt.Errorf("failed to write .ssh/config: %s", err) } - if err := os.Chmod(privateKeyFile, 0600); err != nil { + if err := os.Chmod(privateKeyFile, 0o600); err != nil { return fmt.Errorf("failed to chmod private key file: %s", err) } - if err := os.Chmod(publicKeyFile, 0600); err != nil { + if err := os.Chmod(publicKeyFile, 0o600); err != nil { return fmt.Errorf("failed to chmod public key file: %s", err) } diff --git a/internal/flypg/zombie.go b/internal/flypg/zombie.go index d19122cc..47c77fa9 100644 --- a/internal/flypg/zombie.go +++ b/internal/flypg/zombie.go @@ -31,7 +31,7 @@ func ZombieLockExists() bool { } func writeZombieLock(hostname string) error { - if err := os.WriteFile(zombieLockFile, []byte(hostname), 0600); err != nil { + if err := os.WriteFile(zombieLockFile, []byte(hostname), 0o600); err != nil { return err } @@ -157,6 +157,7 @@ func ZombieDiagnosis(s *DNASample) (string, error) { if s.totalConflicts > 0 { return "", ErrZombieDiagnosisUndecided } + return s.hostname, nil } @@ -257,6 +258,7 @@ func EvaluateClusterState(ctx context.Context, conn *pgx.Conn, node *Node) error return fmt.Errorf("failed to quarantine failed primary: %s", err) } log.Println("[WARN] Primary is going read-only to protect against potential split-brain") + return nil } else if err != nil { return fmt.Errorf("failed to run zombie diagnosis: %s", err) diff --git a/internal/flypg/zombie_test.go b/internal/flypg/zombie_test.go index d757ea6c..41a470df 100644 --- a/internal/flypg/zombie_test.go +++ b/internal/flypg/zombie_test.go @@ -6,7 +6,6 @@ import ( ) func TestZombieDiagnosis(t *testing.T) { - t.Run("SingleMember", func(t *testing.T) { sample := &DNASample{ hostname: "host-1", @@ -24,7 +23,6 @@ func TestZombieDiagnosis(t *testing.T) { if primary != sample.hostname { t.Fatalf("expected %s, got %q", sample.hostname, primary) } - }) t.Run("TwoMember", func(t *testing.T) { @@ -309,5 +307,4 @@ func TestZombieDiagnosis(t *testing.T) { t.Fatalf("expected %s, got %q", "host-99", primary) } }) - } diff --git a/internal/privnet/sixpn.go b/internal/privnet/sixpn.go index ed9d4005..c73012c3 100644 --- a/internal/privnet/sixpn.go +++ b/internal/privnet/sixpn.go @@ -16,7 +16,6 @@ func AllPeers(ctx context.Context, appName string) ([]net.IPAddr, error) { func Get6PN(ctx context.Context, hostname string) ([]net.IPAddr, error) { r := getResolver() ips, err := r.LookupIPAddr(ctx, hostname) - if err != nil { return ips, err } @@ -38,6 +37,7 @@ func Get6PN(ctx context.Context, hostname string) ([]net.IPAddr, error) { if !localExists { ips = append(ips, local[0]) } + return ips, err } @@ -64,6 +64,7 @@ func AllMachines(ctx context.Context, appName string) ([]Machine, error) { machines = append(machines, Machine{Id: parts[0], Region: parts[1]}) } } + return machines, nil } @@ -73,12 +74,14 @@ func getResolver() *net.Resolver { nameserver = "fdaa::3" } nameserver = net.JoinHostPort(nameserver, "53") + return &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, _ string) (net.Conn, error) { d := net.Dialer{ Timeout: 1 * time.Second, } + return d.DialContext(ctx, "udp6", nameserver) }, } diff --git a/internal/supervisor/output.go b/internal/supervisor/output.go index 9cd36017..921a02e2 100644 --- a/internal/supervisor/output.go +++ b/internal/supervisor/output.go @@ -66,6 +66,7 @@ func (m *multiOutput) PipeOutput(proc *process) { if err != io.EOF { log.Printf("reader error: %v", err) } + break } } diff --git a/internal/utils/shell.go b/internal/utils/shell.go index 699eef87..56bd3889 100644 --- a/internal/utils/shell.go +++ b/internal/utils/shell.go @@ -95,6 +95,7 @@ func FileExists(path string) bool { if _, err := os.Stat(path); err != nil { return false } + return true } From c488a5e9d61ed77105ca8cb59812a6a163784b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gra=C3=B1a?= Date: Wed, 18 Mar 2026 10:15:17 -0300 Subject: [PATCH 03/12] golangci-lint fixes: add package comments and fix lint issues --- cmd/flexctl/backups.go | 8 +++++--- internal/api/handler.go | 1 + internal/flybarman/node.go | 1 + internal/flycheck/barman.go | 4 ++-- internal/flycheck/checks.go | 1 + internal/flycheck/pg.go | 6 +++--- internal/flycheck/role.go | 4 ++-- internal/flycheck/vm.go | 6 +++--- internal/flypg/admin/admin.go | 1 + internal/flypg/flypg.go | 1 + internal/flypg/state/store.go | 1 + internal/privnet/sixpn.go | 1 + internal/supervisor/ensure_kill.go | 1 + internal/utils/shell.go | 3 ++- 14 files changed, 25 insertions(+), 14 deletions(-) diff --git a/cmd/flexctl/backups.go b/cmd/flexctl/backups.go index 7e825f39..f026503c 100644 --- a/cmd/flexctl/backups.go +++ b/cmd/flexctl/backups.go @@ -254,7 +254,7 @@ func getAppName() (string, error) { return name, nil } -func getApiUrl() (string, error) { +func getAPIURL() (string, error) { hostname, err := getAppName() if err != nil { return "", err @@ -273,7 +273,7 @@ func newConfigShow() *cobra.Command { return fmt.Errorf("backups are not enabled") } - url, err := getApiUrl() + url, err := getAPIURL() if err != nil { return err } @@ -283,6 +283,7 @@ func newConfigShow() *cobra.Command { if err != nil { return err } + defer resp.Body.Close() // nolint:errcheck var rv configShowResult if err := json.NewDecoder(resp.Body).Decode(&rv); err != nil { @@ -354,7 +355,7 @@ func newConfigUpdate() *cobra.Command { return err } - url, err := getApiUrl() + url, err := getAPIURL() if err != nil { return err } @@ -364,6 +365,7 @@ func newConfigUpdate() *cobra.Command { if err != nil { return err } + defer resp.Body.Close() // nolint:errcheck var rv configUpdateResult if err := json.NewDecoder(resp.Body).Decode(&rv); err != nil { diff --git a/internal/api/handler.go b/internal/api/handler.go index ee37b77a..133d4b69 100644 --- a/internal/api/handler.go +++ b/internal/api/handler.go @@ -1,3 +1,4 @@ +// Package api provides HTTP handlers for the postgres management API. package api import ( diff --git a/internal/flybarman/node.go b/internal/flybarman/node.go index 09e517bb..d58e33b1 100644 --- a/internal/flybarman/node.go +++ b/internal/flybarman/node.go @@ -1,3 +1,4 @@ +// Package flybarman manages barman backup nodes. package flybarman import ( diff --git a/internal/flycheck/barman.go b/internal/flycheck/barman.go index 21f009aa..51e76ec0 100644 --- a/internal/flycheck/barman.go +++ b/internal/flycheck/barman.go @@ -14,7 +14,7 @@ func CheckBarmanConnection(checks *check.CheckSuite) *check.CheckSuite { output, err := cmd.CombinedOutput() if err != nil { - checks.AddCheck("connection", func() (string, error) { + _ = checks.AddCheck("connection", func() (string, error) { msg := "failed running `barman check pg`" return "", errors.New(msg) }) @@ -40,7 +40,7 @@ func CheckBarmanConnection(checks *check.CheckSuite) *check.CheckSuite { continue } - checks.AddCheck(left, func() (string, error) { + _ = checks.AddCheck(left, func() (string, error) { if strings.Contains(right, "FAILED") { return "", errors.New(right) } diff --git a/internal/flycheck/checks.go b/internal/flycheck/checks.go index 543aa188..2cda0353 100644 --- a/internal/flycheck/checks.go +++ b/internal/flycheck/checks.go @@ -1,3 +1,4 @@ +// Package flycheck implements health check handlers for fly.io postgres. package flycheck import ( diff --git a/internal/flycheck/pg.go b/internal/flycheck/pg.go index 06465b3d..5bca9239 100644 --- a/internal/flycheck/pg.go +++ b/internal/flycheck/pg.go @@ -41,13 +41,13 @@ func CheckPostgreSQL(ctx context.Context, checks *check.CheckSuite) (*check.Chec _ = repConn.Close(ctx) } - checks.AddCheck("connections", func() (string, error) { + _ = checks.AddCheck("connections", func() (string, error) { return connectionCount(ctx, localConn) }) if member.Role == flypg.PrimaryRoleName { // Check to see if any locks are present. - checks.AddCheck("cluster-locks", func() (string, error) { + _ = checks.AddCheck("cluster-locks", func() (string, error) { if flypg.ZombieLockExists() { return "", fmt.Errorf("`zombie.lock` detected") } @@ -62,7 +62,7 @@ func CheckPostgreSQL(ctx context.Context, checks *check.CheckSuite) (*check.Chec if member.Active { // Check that provides additional insight into disk capacity and // how close we are to hitting the readonly threshold. - checks.AddCheck("disk-capacity", func() (string, error) { + _ = checks.AddCheck("disk-capacity", func() (string, error) { return diskCapacityCheck(ctx, node) }) } diff --git a/internal/flycheck/role.go b/internal/flycheck/role.go index 0bee5d1e..9ff20333 100644 --- a/internal/flycheck/role.go +++ b/internal/flycheck/role.go @@ -13,7 +13,7 @@ import ( // PostgreSQLRole outputs current role func PostgreSQLRole(ctx context.Context, checks *check.CheckSuite) (*check.CheckSuite, error) { if os.Getenv("IS_BARMAN") != "" { - checks.AddCheck("role", func() (string, error) { + _ = checks.AddCheck("role", func() (string, error) { return "barman", nil }) @@ -35,7 +35,7 @@ func PostgreSQLRole(ctx context.Context, checks *check.CheckSuite) (*check.Check _ = conn.Close(ctx) } - checks.AddCheck("role", func() (string, error) { + _ = checks.AddCheck("role", func() (string, error) { if flypg.ZombieLockExists() { return "zombie", nil } diff --git a/internal/flycheck/vm.go b/internal/flycheck/vm.go index 98862e5e..80249e75 100644 --- a/internal/flycheck/vm.go +++ b/internal/flycheck/vm.go @@ -15,16 +15,16 @@ import ( // CheckVM for system / disk checks func CheckVM(checks *check.CheckSuite) *check.CheckSuite { - checks.AddCheck("checkDisk", func() (string, error) { + _ = checks.AddCheck("checkDisk", func() (string, error) { return checkDisk("/data/") }) - checks.AddCheck("checkLoad", checkLoad) + _ = checks.AddCheck("checkLoad", checkLoad) pressureNames := []string{"memory", "cpu", "io"} for _, n := range pressureNames { name := n - checks.AddCheck(name, func() (string, error) { + _ = checks.AddCheck(name, func() (string, error) { return checkPressure(name) }) } diff --git a/internal/flypg/admin/admin.go b/internal/flypg/admin/admin.go index fdd8bf94..5ef6fc97 100644 --- a/internal/flypg/admin/admin.go +++ b/internal/flypg/admin/admin.go @@ -1,3 +1,4 @@ +// Package admin provides administrative utilities for managing postgres. package admin import ( diff --git a/internal/flypg/flypg.go b/internal/flypg/flypg.go index 7ded328f..ab0582ae 100644 --- a/internal/flypg/flypg.go +++ b/internal/flypg/flypg.go @@ -1,3 +1,4 @@ +// Package flypg manages the postgres cluster lifecycle on fly.io. package flypg import ( diff --git a/internal/flypg/state/store.go b/internal/flypg/state/store.go index 77759d6a..2efe9458 100644 --- a/internal/flypg/state/store.go +++ b/internal/flypg/state/store.go @@ -1,3 +1,4 @@ +// Package state manages the persistent state store for the flypg cluster. package state import ( diff --git a/internal/privnet/sixpn.go b/internal/privnet/sixpn.go index c73012c3..2c97348a 100644 --- a/internal/privnet/sixpn.go +++ b/internal/privnet/sixpn.go @@ -1,3 +1,4 @@ +// Package privnet provides utilities for working with private networks. package privnet import ( diff --git a/internal/supervisor/ensure_kill.go b/internal/supervisor/ensure_kill.go index 0cfdbc8a..e75c41bc 100644 --- a/internal/supervisor/ensure_kill.go +++ b/internal/supervisor/ensure_kill.go @@ -1,6 +1,7 @@ //go:build !linux // +build !linux +// Package supervisor manages the lifecycle of supervised processes. package supervisor import "os/exec" diff --git a/internal/utils/shell.go b/internal/utils/shell.go index 56bd3889..86411a74 100644 --- a/internal/utils/shell.go +++ b/internal/utils/shell.go @@ -1,3 +1,4 @@ +// Package utils provides shared utility functions. package utils import ( @@ -43,7 +44,7 @@ func RunCmd(ctx context.Context, usr string, name string, args ...string) ([]byt return cmd.Output() } -// Deprecated, use RunCmd instead +// RunCommand runs a shell command as the given user. Deprecated, use RunCmd instead. func RunCommand(cmdStr, usr string) ([]byte, error) { uid, gid, err := SystemUserIDs(usr) if err != nil { From 55d63abb06b9515bc786279e3e7e936cee8e4134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gra=C3=B1a?= Date: Wed, 18 Mar 2026 10:16:40 -0300 Subject: [PATCH 04/12] bump go to 1.26 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 09e9dbc2..60ea0742 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/fly-apps/postgres-flex -go 1.23 +go 1.26 require ( github.com/go-chi/chi/v5 v5.0.8 From 5b058eb9bf2298bace23bbd5828f6d35c8201ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gra=C3=B1a?= Date: Wed, 18 Mar 2026 10:18:59 -0300 Subject: [PATCH 05/12] bump golang base image to 1.26 in all Dockerfiles --- pg15/Dockerfile | 2 +- pg15/Dockerfile-timescaledb | 2 +- pg16/Dockerfile | 2 +- pg16/Dockerfile-timescaledb | 2 +- pg17/Dockerfile | 2 +- pg17/Dockerfile-timescaledb | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pg15/Dockerfile b/pg15/Dockerfile index 9611a1e5..dc8dbd2c 100644 --- a/pg15/Dockerfile +++ b/pg15/Dockerfile @@ -2,7 +2,7 @@ ARG PG_VERSION=15.10 ARG PG_MAJOR_VERSION=15 ARG VERSION=custom -FROM golang:1.23 +FROM golang:1.26 WORKDIR /go/src/github.com/fly-apps/fly-postgres COPY . . diff --git a/pg15/Dockerfile-timescaledb b/pg15/Dockerfile-timescaledb index 78c69425..34447488 100644 --- a/pg15/Dockerfile-timescaledb +++ b/pg15/Dockerfile-timescaledb @@ -2,7 +2,7 @@ ARG PG_VERSION=15.10 ARG PG_MAJOR_VERSION=15 ARG VERSION=custom -FROM golang:1.23 +FROM golang:1.26 WORKDIR /go/src/github.com/fly-apps/fly-postgres COPY . . diff --git a/pg16/Dockerfile b/pg16/Dockerfile index 0ec89145..c01706f4 100644 --- a/pg16/Dockerfile +++ b/pg16/Dockerfile @@ -2,7 +2,7 @@ ARG PG_VERSION=16.6 ARG PG_MAJOR_VERSION=16 ARG VERSION=custom -FROM golang:1.23 AS builder +FROM golang:1.26 AS builder WORKDIR /go/src/github.com/fly-apps/fly-postgres COPY . . diff --git a/pg16/Dockerfile-timescaledb b/pg16/Dockerfile-timescaledb index cdb92ebb..fec29b5f 100644 --- a/pg16/Dockerfile-timescaledb +++ b/pg16/Dockerfile-timescaledb @@ -2,7 +2,7 @@ ARG PG_VERSION=16.6 ARG PG_MAJOR_VERSION=16 ARG VERSION=custom -FROM golang:1.23 AS builder +FROM golang:1.26 AS builder WORKDIR /go/src/github.com/fly-apps/fly-postgres COPY . . diff --git a/pg17/Dockerfile b/pg17/Dockerfile index 07c6c825..6b311196 100644 --- a/pg17/Dockerfile +++ b/pg17/Dockerfile @@ -2,7 +2,7 @@ ARG PG_VERSION=17.2 ARG PG_MAJOR_VERSION=17 ARG VERSION=custom -FROM golang:1.23 AS builder +FROM golang:1.26 AS builder WORKDIR /go/src/github.com/fly-apps/fly-postgres COPY . . diff --git a/pg17/Dockerfile-timescaledb b/pg17/Dockerfile-timescaledb index d0216f8d..e4ab8a09 100644 --- a/pg17/Dockerfile-timescaledb +++ b/pg17/Dockerfile-timescaledb @@ -2,7 +2,7 @@ ARG PG_VERSION=17.2 ARG PG_MAJOR_VERSION=17 ARG VERSION=custom -FROM golang:1.23 AS builder +FROM golang:1.26 AS builder WORKDIR /go/src/github.com/fly-apps/fly-postgres COPY . . From 86e35143e66a04581502160ba904afea90329756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gra=C3=B1a?= Date: Wed, 18 Mar 2026 10:21:10 -0300 Subject: [PATCH 06/12] update dependabot --- .github/dependabot.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4f2ea0fb..0591ab89 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,26 @@ -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: + - package-ecosystem: "gomod" + directory: "/" + labels: + - "dependencies" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + groups: + tracing: + patterns: + - "go.opentelemetry.io/*" + golangx: + patterns: + - "golang.org/x/*" + - package-ecosystem: "github-actions" directory: "/" + labels: + - "dependencies" schedule: interval: "weekly" + cooldown: + default-days: 7 From 991041bb52a9f5f15ef731733ad9b84218f584c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gra=C3=B1a?= Date: Wed, 18 Mar 2026 11:02:56 -0300 Subject: [PATCH 07/12] build docker images on pull requests without pushing --- .github/workflows/ci.yaml | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ae79e889..a5b158a6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,10 +2,11 @@ name: Release on: create: + pull_request: jobs: release: - if: ${{ startsWith(github.ref, 'refs/tags/v') }} + if: ${{ startsWith(github.ref, 'refs/tags/v') || github.event_name == 'pull_request' }} runs-on: ubuntu-latest steps: @@ -15,12 +16,14 @@ jobs: version: v0.9.1 - uses: docker/login-action@v3 + if: github.event_name != 'pull_request' with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - uses: actions/checkout@v6 - id: get-latest-tag + if: github.event_name != 'pull_request' uses: "WyriHaximus/github-action-get-previous-tag@v2" - name: Build and push Postgres 15 @@ -30,10 +33,10 @@ jobs: build-args: | PG_VERSION=15.10 PG_MAJOR_VERSION=15 - VERSION=${{ steps.get-latest-tag.outputs.tag }} + VERSION=${{ steps.get-latest-tag.outputs.tag || 'pr-build' }} context: . file: ./pg15/Dockerfile - push: true + push: ${{ github.event_name != 'pull_request' }} tags: | flyio/postgres-flex:15 flyio/postgres-flex:15.10 @@ -45,10 +48,10 @@ jobs: build-args: | PG_VERSION=15.10 PG_MAJOR_VERSION=15 - VERSION=${{ steps.get-latest-tag.outputs.tag }} + VERSION=${{ steps.get-latest-tag.outputs.tag || 'pr-build' }} context: . file: ./pg15/Dockerfile-timescaledb - push: true + push: ${{ github.event_name != 'pull_request' }} tags: | flyio/postgres-flex-timescaledb:15 flyio/postgres-flex-timescaledb:15.10 @@ -60,10 +63,10 @@ jobs: build-args: | PG_VERSION=16.6 PG_MAJOR_VERSION=16 - VERSION=${{ steps.get-latest-tag.outputs.tag }} + VERSION=${{ steps.get-latest-tag.outputs.tag || 'pr-build' }} context: . file: ./pg16/Dockerfile - push: true + push: ${{ github.event_name != 'pull_request' }} tags: | flyio/postgres-flex:16 flyio/postgres-flex:16.6 @@ -75,10 +78,10 @@ jobs: build-args: | PG_VERSION=16.6 PG_MAJOR_VERSION=16 - VERSION=${{ steps.get-latest-tag.outputs.tag }} + VERSION=${{ steps.get-latest-tag.outputs.tag || 'pr-build' }} context: . file: ./pg16/Dockerfile-timescaledb - push: true + push: ${{ github.event_name != 'pull_request' }} tags: | flyio/postgres-flex-timescaledb:16 flyio/postgres-flex-timescaledb:16.6 @@ -90,10 +93,10 @@ jobs: build-args: | PG_VERSION=17.2 PG_MAJOR_VERSION=17 - VERSION=${{ steps.get-latest-tag.outputs.tag }} + VERSION=${{ steps.get-latest-tag.outputs.tag || 'pr-build' }} context: . file: ./pg17/Dockerfile - push: true + push: ${{ github.event_name != 'pull_request' }} tags: | flyio/postgres-flex:17 flyio/postgres-flex:17.2 @@ -105,10 +108,10 @@ jobs: build-args: | PG_VERSION=17.2 PG_MAJOR_VERSION=17 - VERSION=${{ steps.get-latest-tag.outputs.tag }} + VERSION=${{ steps.get-latest-tag.outputs.tag || 'pr-build' }} context: . file: ./pg17/Dockerfile-timescaledb - push: true + push: ${{ github.event_name != 'pull_request' }} tags: | flyio/postgres-flex-timescaledb:17 flyio/postgres-flex-timescaledb:17.2 From b71fd51ec2cec2239034ac826955d768b2766baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gra=C3=B1a?= Date: Wed, 18 Mar 2026 11:05:13 -0300 Subject: [PATCH 08/12] replace create trigger with push tags and drop redundant job condition --- .github/workflows/ci.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a5b158a6..0fb45495 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,12 +1,13 @@ name: Release on: - create: + push: + tags: + - 'v*' pull_request: jobs: release: - if: ${{ startsWith(github.ref, 'refs/tags/v') || github.event_name == 'pull_request' }} runs-on: ubuntu-latest steps: From e88ef340684a587b0515d89faf6d41743e476ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gra=C3=B1a?= Date: Wed, 18 Mar 2026 11:08:17 -0300 Subject: [PATCH 09/12] go fix: modernize code (interface{} -> any, SplitSeq, fmt.Appendf, drop build constraints) --- cmd/flexctl/backups.go | 2 +- internal/api/handle_admin.go | 15 ++++++--------- internal/api/response.go | 2 +- internal/api/types.go | 4 ++-- internal/flycheck/barman.go | 4 ++-- internal/flypg/admin/admin.go | 8 ++++---- internal/privnet/sixpn.go | 4 ++-- internal/supervisor/ensure_kill.go | 1 - internal/supervisor/ensure_kill_linux.go | 1 - internal/supervisor/output.go | 5 ++--- internal/supervisor/process.go | 4 ++-- internal/supervisor/supervisor.go | 2 +- internal/supervisor/utils.go | 2 +- 13 files changed, 24 insertions(+), 30 deletions(-) diff --git a/cmd/flexctl/backups.go b/cmd/flexctl/backups.go index f026503c..f2d50627 100644 --- a/cmd/flexctl/backups.go +++ b/cmd/flexctl/backups.go @@ -308,7 +308,7 @@ type successfulUpdateResult struct { } type configUpdateResult struct { - Result successfulUpdateResult `json:"result,omitempty"` + Result successfulUpdateResult `json:"result"` Error string `json:"error,omitempty"` } diff --git a/internal/api/handle_admin.go b/internal/api/handle_admin.go index 7148dcf1..7c9723f1 100644 --- a/internal/api/handle_admin.go +++ b/internal/api/handle_admin.go @@ -3,6 +3,7 @@ package api import ( "encoding/json" "fmt" + "maps" "net/http" "os" "strings" @@ -145,7 +146,7 @@ func handleUpdatePostgresSettings(w http.ResponseWriter, r *http.Request) { return } - var requestedChanges map[string]interface{} + var requestedChanges map[string]any if err := json.NewDecoder(r.Body).Decode(&requestedChanges); err != nil { renderErr(w, err) return @@ -158,9 +159,7 @@ func handleUpdatePostgresSettings(w http.ResponseWriter, r *http.Request) { return } - for k, v := range requestedChanges { - cfg[k] = v - } + maps.Copy(cfg, requestedChanges) node.PGConfig.SetUserConfig(cfg) @@ -287,7 +286,7 @@ func handleViewRepmgrSettings(w http.ResponseWriter, r *http.Request) { return } - out := map[string]interface{}{} + out := map[string]any{} for key := range all { val := all[key] @@ -362,7 +361,7 @@ func handleUpdateBarmanSettings(w http.ResponseWriter, r *http.Request) { return } - var requestedChanges map[string]interface{} + var requestedChanges map[string]any if err := json.NewDecoder(r.Body).Decode(&requestedChanges); err != nil { renderErr(w, err) return @@ -373,9 +372,7 @@ func handleUpdateBarmanSettings(w http.ResponseWriter, r *http.Request) { return } - for k, v := range requestedChanges { - cfg[k] = v - } + maps.Copy(cfg, requestedChanges) barman.SetUserConfig(cfg) diff --git a/internal/api/response.go b/internal/api/response.go index 9c11b061..4ec85011 100644 --- a/internal/api/response.go +++ b/internal/api/response.go @@ -10,7 +10,7 @@ import ( "github.com/jackc/pgx/v5" ) -func renderJSON(w http.ResponseWriter, data interface{}, status int) { +func renderJSON(w http.ResponseWriter, data any, status int) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) if err := json.NewEncoder(w).Encode(data); err != nil { diff --git a/internal/api/types.go b/internal/api/types.go index 9f652e7e..cf1a1fb4 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -16,6 +16,6 @@ type errRes struct { } type Response struct { - Result interface{} `json:"result,omitempty"` - Error string `json:"error,omitempty"` + Result any `json:"result,omitempty"` + Error string `json:"error,omitempty"` } diff --git a/internal/flycheck/barman.go b/internal/flycheck/barman.go index 51e76ec0..a5c95555 100644 --- a/internal/flycheck/barman.go +++ b/internal/flycheck/barman.go @@ -25,9 +25,9 @@ func CheckBarmanConnection(checks *check.CheckSuite) *check.CheckSuite { // Each line besides the first represents a check and will include FAILED or OK // We just separate those lines and create a health check entry of our own // so it's uniform how we handle it - lines := strings.Split(string(output), "\n") + lines := strings.SplitSeq(string(output), "\n") - for _, line := range lines { + for line := range lines { pattern := `\s*(.*?):(.*)$` regex := regexp.MustCompile(pattern) matches := regex.FindStringSubmatch(line) diff --git a/internal/flypg/admin/admin.go b/internal/flypg/admin/admin.go index 5ef6fc97..a8efee25 100644 --- a/internal/flypg/admin/admin.go +++ b/internal/flypg/admin/admin.go @@ -330,7 +330,7 @@ func DropOwned(ctx context.Context, conn *pgx.Conn, user string) error { return nil } -func SetConfigurationSetting(ctx context.Context, conn *pgx.Conn, key string, value interface{}) error { +func SetConfigurationSetting(ctx context.Context, conn *pgx.Conn, key string, value any) error { sql := fmt.Sprintf("SET %s to %s", key, value) _, err := conn.Exec(ctx, sql) return err @@ -400,7 +400,7 @@ func GetSetting(ctx context.Context, pg *pgx.Conn, setting string) (*PGSetting, return &out, nil } -func ValidatePGSettings(ctx context.Context, conn *pgx.Conn, requested map[string]interface{}) error { +func ValidatePGSettings(ctx context.Context, conn *pgx.Conn, requested map[string]any) error { for k, v := range requested { exists, err := SettingExists(ctx, conn, k) if err != nil { @@ -413,8 +413,8 @@ func ValidatePGSettings(ctx context.Context, conn *pgx.Conn, requested map[strin // Verify specified extensions are installed if k == "shared_preload_libraries" { extensions := strings.Trim(v.(string), "'") - extSlice := strings.Split(extensions, ",") - for _, e := range extSlice { + extSlice := strings.SplitSeq(extensions, ",") + for e := range extSlice { available, err := ExtensionAvailable(ctx, conn, e) if err != nil { return fmt.Errorf("failed to verify pg extension %s: %s", e, err) diff --git a/internal/privnet/sixpn.go b/internal/privnet/sixpn.go index 2c97348a..5ccb9c60 100644 --- a/internal/privnet/sixpn.go +++ b/internal/privnet/sixpn.go @@ -56,8 +56,8 @@ func AllMachines(ctx context.Context, appName string) ([]Machine, error) { machines := make([]Machine, 0) for _, txt := range txts { - parts := strings.Split(txt, ",") - for _, part := range parts { + parts := strings.SplitSeq(txt, ",") + for part := range parts { parts := strings.Split(part, " ") if len(parts) != 2 { return nil, fmt.Errorf("invalid machine DNS TXT format: %s", txt) diff --git a/internal/supervisor/ensure_kill.go b/internal/supervisor/ensure_kill.go index e75c41bc..2b6d8411 100644 --- a/internal/supervisor/ensure_kill.go +++ b/internal/supervisor/ensure_kill.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux // Package supervisor manages the lifecycle of supervised processes. package supervisor diff --git a/internal/supervisor/ensure_kill_linux.go b/internal/supervisor/ensure_kill_linux.go index df95c564..93eae64e 100644 --- a/internal/supervisor/ensure_kill_linux.go +++ b/internal/supervisor/ensure_kill_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux package supervisor diff --git a/internal/supervisor/output.go b/internal/supervisor/output.go index 921a02e2..8464ce33 100644 --- a/internal/supervisor/output.go +++ b/internal/supervisor/output.go @@ -109,7 +109,6 @@ func (m *multiOutput) ClosePipe(proc *process) { } func (m *multiOutput) WriteErr(proc *process, err error) { - m.WriteLine(proc, []byte( - fmt.Sprintf("\033[0;31m%v\033[0m", err), - )) + m.WriteLine(proc, + fmt.Appendf(nil, "\033[0;31m%v\033[0m", err)) } diff --git a/internal/supervisor/process.go b/internal/supervisor/process.go index 9d31c6a3..e84bb3d6 100644 --- a/internal/supervisor/process.go +++ b/internal/supervisor/process.go @@ -98,13 +98,13 @@ func (p *process) Run() { p.writeErr(err) } else { status := p.cmd.ProcessState.ExitCode() - p.writeLine([]byte(fmt.Sprintf("\033[1mProcess exited %d\033[0m", status))) + p.writeLine(fmt.Appendf(nil, "\033[1mProcess exited %d\033[0m", status)) } } func (p *process) Interrupt() { if p.Running() { - p.writeLine([]byte(fmt.Sprintf("\033[1mStopping %s...\033[0m", p.stopSignal))) + p.writeLine(fmt.Appendf(nil, "\033[1mStopping %s...\033[0m", p.stopSignal)) p.signal(p.stopSignal) } } diff --git a/internal/supervisor/supervisor.go b/internal/supervisor/supervisor.go index fffc5794..73ed6858 100644 --- a/internal/supervisor/supervisor.go +++ b/internal/supervisor/supervisor.go @@ -94,7 +94,7 @@ func (*Supervisor) runProcess(ctx context.Context, proc *process) error { } restarts++ - proc.writeLine([]byte(fmt.Sprintf("restarting in %s [attempt %d]", proc.restartDelay, restarts))) + proc.writeLine(fmt.Appendf(nil, "restarting in %s [attempt %d]", proc.restartDelay, restarts)) select { case <-time.After(proc.restartDelay): case <-ctx.Done(): diff --git a/internal/supervisor/utils.go b/internal/supervisor/utils.go index e455bb01..7b9f5966 100644 --- a/internal/supervisor/utils.go +++ b/internal/supervisor/utils.go @@ -11,7 +11,7 @@ func fatalOnErr(err error) { } } -func fatal(i ...interface{}) { +func fatal(i ...any) { fmt.Fprint(os.Stderr, "hivemind: ") fmt.Fprintln(os.Stderr, i...) panic(i) From acf1d4e65d84e88789e18d28a54979bd8ae187e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gra=C3=B1a?= Date: Wed, 18 Mar 2026 11:12:10 -0300 Subject: [PATCH 10/12] fix eol --- bin/barman_cron | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/barman_cron b/bin/barman_cron index 0a83286e..1a54438d 100755 --- a/bin/barman_cron +++ b/bin/barman_cron @@ -15,4 +15,4 @@ barman_exit_code=$? if [ $barman_exit_code -ne 0 ]; then echo "Barman cron failed with exit code $barman_exit_code: $barman_output" >> /proc/$cron_pid/fd/2 exit 1 -fi \ No newline at end of file +fi From 1e2d27c82fdd91a81a7d0ec4426e0e2b62d2f01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gra=C3=B1a?= Date: Wed, 18 Mar 2026 11:25:42 -0300 Subject: [PATCH 11/12] move supervisor package comment to unconditional file --- internal/supervisor/ensure_kill.go | 1 - internal/supervisor/output.go | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/supervisor/ensure_kill.go b/internal/supervisor/ensure_kill.go index 2b6d8411..ce1bb81b 100644 --- a/internal/supervisor/ensure_kill.go +++ b/internal/supervisor/ensure_kill.go @@ -1,6 +1,5 @@ //go:build !linux -// Package supervisor manages the lifecycle of supervised processes. package supervisor import "os/exec" diff --git a/internal/supervisor/output.go b/internal/supervisor/output.go index 8464ce33..b3801c9d 100644 --- a/internal/supervisor/output.go +++ b/internal/supervisor/output.go @@ -1,3 +1,4 @@ +// Package supervisor manages the lifecycle of supervised processes. package supervisor import ( From 6fdaf08c0fbe8a3c63c5bd897763ceba0b90bb01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gra=C3=B1a?= Date: Wed, 18 Mar 2026 11:36:49 -0300 Subject: [PATCH 12/12] add unparam linter --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index 98f7af0d..31edaef3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,6 +15,7 @@ linters: - gocheckcompilerdirectives - errname - errchkjson + - unparam settings: nlreturn: block-size: 3