Help us shape the future of Go! Take the 2026 Go Developer Survey

devpg

package
v1.0.14 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 2, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package devpg owns ONE fact: the host coordinates of the dev postgres a forge project's docker-compose publishes.

Why this package exists

That fact used to be declared in two places that could disagree:

  • docker-compose.yml published postgres on `${POSTGRES_PORT:-5432}`, and
  • the scaffolded DATABASE_URL secret pinned an ABSOLUTE DSN on a hardcoded port (5434).

The absolute URL wins at runtime, so the two disagreed by DEFAULT — and silently. `POSTGRES_PORT=15433 forge run` started THIS project's postgres on 15433 and then connected the app to whatever happened to be listening on 5434: another stack's database. forge created the database there, migrated it, seeded it, printed a healthy banner and exited 0. The only evidence was rows in a database the project did not own.

The fix is to stop declaring the fact twice. This package derives the dev postgres coordinates from the SAME environment variables (and the same defaults) that docker-compose itself reads, so the scaffolded DSN and the container's published port are two renderings of one input rather than two independent claims. Reconcile then checks that they still agree at run time, because a project scaffolded under one POSTGRES_PORT can be run under another — a correct default alone cannot close that gap.

Index

Constants

View Source
const (
	// PortEnv is the env var compose expands for postgres's published host port.
	PortEnv = "POSTGRES_PORT"
	// DefaultPort is compose's `${POSTGRES_PORT:-5432}` fallback.
	DefaultPort = "5432"

	// UserEnv / DefaultUser and PasswordEnv / DefaultPassword mirror the
	// POSTGRES_USER / POSTGRES_PASSWORD the compose postgres is initialised with.
	UserEnv         = "POSTGRES_USER"
	DefaultUser     = "postgres"
	PasswordEnv     = "POSTGRES_PASSWORD"
	DefaultPassword = "postgres"
)

The compose variables and defaults. These MUST stay identical to the scaffold's docker-compose.yml postgres service (internal/templates/project/docker-compose.yml.tmpl); the whole point of this package is that both sides read one set of names and defaults.

View Source
const ComposeEnvFilesVar = "COMPOSE_ENV_FILES"

ComposeEnvFilesVar is compose's own override for which dotenv files it loads. When set it REPLACES the default `.env` (comma-separated, later files winning), exactly as `--env-file` does.

View Source
const ComposeFileName = "docker-compose.yml"

ComposeFileName is the compose file the scaffold writes at the project root.

View Source
const DotEnvFileName = ".env"

DotEnvFileName is the file `docker compose` auto-loads from the project directory for its OWN interpolation, in addition to the shell environment. Forgetting this file is what made the guard refuse correct projects: a developer who writes POSTGRES_PORT=5460 here (rather than exporting it) really does get postgres published on 5460.

Variables

This section is empty.

Functions

func ComposePublishedPort

func ComposePublishedPort(projectDir string) string

ComposePublishedPort returns the HOST port the project's compose file publishes for its postgres service, with compose's own `${VAR:-default}` expansion applied against the same inputs compose reads — i.e. the port the container will actually bind when `docker compose up` runs here.

Returns "" when there is no compose file, no postgres service, or no parseable published port; every caller treats that as "nothing to reconcile against" rather than an error, because a project may legitimately run its dev database out of band.

func DSN

func DSN(database string) string

DSN is the connection string for the named database on the dev postgres — the value the scaffold seeds into the env's secret store as DATABASE_URL. Built from Port/User/Password so it names the port compose actually publishes.

func DatabaseOf

func DatabaseOf(dsn string) string

DatabaseOf extracts the database name from a postgres DSN, or "".

func HostOf

func HostOf(dsn string) string

HostOf extracts the hostname (no port) from a postgres DSN, or "".

func Password

func Password() string

Password is the dev postgres password: $POSTGRES_PASSWORD, else "postgres".

func Port

func Port() string

Port is the host port the dev postgres publishes: $POSTGRES_PORT, else compose's 5432 default.

func PortOf

func PortOf(dsn string) string

PortOf extracts the host port from a postgres DSN. A DSN that will not parse, names no host, or carries no explicit port yields "".

func Reconcile

func Reconcile(dsn, composePort string) error

Reconcile reports whether dsn agrees with the port compose publishes for this project's postgres, returning a runbook error when it does not.

This is the guard that makes the corruption impossible rather than unlikely. A project scaffolded when POSTGRES_PORT was unset carries a DSN on 5432; run it later under POSTGRES_PORT=15433 and compose moves the database while the DSN does not follow. Rather than let the app connect to whatever else is listening on 5432 — on a developer machine running several stacks, that is reliably SOMEONE ELSE'S postgres — forge stops and names both values.

composePort is the port the project's compose file publishes (see ComposePublishedPort). An empty composePort or a non-loopback / port-less DSN means there is nothing to reconcile against, and the check passes.

func ResolveComposePort

func ResolveComposePort(projectDir string, envFiles []string) (port, unknownReason string)

ResolveComposePort resolves the published host port of the project's postgres service the way `docker compose` itself would, and reports the case where forge CANNOT know.

envFiles are the `--env-file` paths forge will pass to compose for this service (see internal/deploytarget/compose.go, which forwards a service's declared env_file). Passing --env-file REPLACES the default `.env`, so when forge knows it will pass one, that file — not `.env` — is what compose interpolates from. Pass nil for the ordinary path.

The second return is a REASON the guard should stand down: non-empty means forge could not faithfully reproduce compose's interpolation (an env file it cannot read), so the port it computed may be wrong. Refusing on a value forge is not sure of is the failure mode this guard must avoid — a guard that fires on a correct configuration teaches people to route around it. Callers skip the check and say why.

func ResolveComposeServicePort

func ResolveComposeServicePort(projectDir, service string, envFiles []string) (port, unknownReason string)

ResolveComposeServicePort is ResolveComposePort for any service in the project's compose file, not just postgres.

The interpolation rules are the service-independent half of this package — compose expands `${VAR:-default}` the same way whichever service the mapping belongs to — so a second caller that needs a published host port (the dev IdP's, to reach the instance the browser reaches) asks here rather than reimplementing the precedence and getting it subtly wrong.

func User

func User() string

User is the dev postgres superuser: $POSTGRES_USER, else "postgres".

Types

type MismatchError

type MismatchError struct {
	// DSNPort is the port DATABASE_URL names.
	DSNPort string
	// ComposePort is the port docker-compose publishes postgres on.
	ComposePort string
	// Database is the database name in the DSN (may be "").
	Database string
}

MismatchError reports a DATABASE_URL whose port is not the port this project's compose publishes postgres on. Its message is a runbook: it names both values, says what would have happened, and gives the literal fix.

func (*MismatchError) Error

func (e *MismatchError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL