Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4233e3e05 | ||
|
|
e946709e82 | ||
|
|
a447523749 |
@@ -53,10 +53,12 @@ jobs:
|
||||
|
||||
- name: Derive image tags
|
||||
id: meta
|
||||
env:
|
||||
# signals#47: a ref name can carry $(...); pass it via env, not ${{ }}.
|
||||
REF: ${{ github.ref }}
|
||||
run: |
|
||||
SHA=$(git rev-parse --short HEAD)
|
||||
echo "sha-tag=${SHA}" >> "$GITHUB_OUTPUT"
|
||||
REF="${{ github.ref }}"
|
||||
if [[ "$REF" == refs/tags/v* ]]; then
|
||||
echo "version-tag=${REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
@@ -27,6 +27,23 @@ docs it indexes.
|
||||
- **Language is Go (ADR-001).** Do not introduce Python or a second language. If you think you
|
||||
need one, that's a new ADR with a real justification, not a default.
|
||||
|
||||
## Deploying: a push does not deploy
|
||||
|
||||
Tapir runs on the koala k3s cluster under Flux GitOps. Pushing here only builds the image;
|
||||
`kubectl set image` is reverted by Flux drift correction within seconds. The manifest bump is
|
||||
manual: Flux image-automation in `mathias/infra` covers only `supervisor`, not tapir (infra#111
|
||||
is closed, but `k3s/flux/flux-system/image-automation.yaml` has no tapir policy).
|
||||
|
||||
1. CI (`.gitea/workflows/ci.yml`) builds and pushes `localhost:5000/tapir:<short-sha>` on the
|
||||
main push.
|
||||
2. In `mathias/infra`, bump `image:` in `k3s/apps/tapir/deployment.yaml` to that sha; commit to
|
||||
`main` (infra's own rules and full `task check` apply).
|
||||
3. On koala: `flux reconcile kustomization apps --with-source`, then
|
||||
`kubectl -n tapir rollout status deploy/tapir`. Both need per-action approval in a session.
|
||||
4. Confirm the running image is the new sha and the startup log shows the new code.
|
||||
|
||||
Runbook and why: brain `homelab/failures/flux-reverts-kubectl-set-image-deploy-via-git`.
|
||||
|
||||
## Things that look reusable but are NOT — read before "reusing"
|
||||
|
||||
These caused real mistakes that were caught and corrected; the corrections are load-bearing.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestMain's Postgres already holds a port. A second instance started in the
|
||||
// same process must still come up, i.e. the port is not derived from something
|
||||
// two starters share (the PID). Observes: startTestPostgres succeeds while
|
||||
// another instance from this process is listening.
|
||||
func TestStartTestPostgres_SecondInstanceStartsWhileFirstHoldsItsPort(t *testing.T) {
|
||||
pg, _, rt, err := startTestPostgres(fmt.Sprintf("tapir-epg-store-second-%d", os.Getpid()))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = pg.Stop(); _ = os.RemoveAll(rt) })
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package store_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -25,27 +26,15 @@ var _ ports.Sink = (*store.Store)(nil)
|
||||
var dsn string
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Port + runtime/data dirs are per-process (PID-derived) so two concurrent
|
||||
// `go test` invocations — e.g. a push-run and a tag-run firing together in CI —
|
||||
// don't collide on a fixed port or a shared data dir (which silently failed
|
||||
// both runs). CachePath is shared so the PG archive is downloaded once, not
|
||||
// per process. Base 54000 keeps this package's range distinct from web's.
|
||||
port := uint32(54000 + os.Getpid()%1000)
|
||||
dsn = fmt.Sprintf("postgres://postgres:postgres@localhost:%d/postgres?sslmode=disable", port)
|
||||
|
||||
rt := filepath.Join(os.TempDir(), fmt.Sprintf("tapir-epg-store-%d", os.Getpid()))
|
||||
pg := embeddedpostgres.NewDatabase(
|
||||
embeddedpostgres.DefaultConfig().
|
||||
Port(port).
|
||||
RuntimePath(rt).
|
||||
DataPath(filepath.Join(rt, "data")).
|
||||
BinariesPath(filepath.Join(rt, "bin")).
|
||||
CachePath(filepath.Join(os.TempDir(), "tapir-epg-cache")),
|
||||
)
|
||||
if err := pg.Start(); err != nil {
|
||||
// Runtime/data dirs are per-process (PID-named) and the port is OS-assigned,
|
||||
// so concurrent `go test` runs on a shared CI host don't collide on either.
|
||||
// CachePath is shared so the PG archive is downloaded once, not per process.
|
||||
pg, port, rt, err := startTestPostgres(fmt.Sprintf("tapir-epg-store-%d", os.Getpid()))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "embedded-postgres start: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
dsn = fmt.Sprintf("postgres://postgres:postgres@localhost:%d/postgres?sslmode=disable", port)
|
||||
|
||||
code := m.Run()
|
||||
|
||||
@@ -56,6 +45,44 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// freePort asks the OS for an unused TCP port. The port is released before
|
||||
// return, so another process can grab it first; startTestPostgres retries.
|
||||
func freePort() (uint32, error) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("listen for free port: %w", err)
|
||||
}
|
||||
defer func() { _ = l.Close() }()
|
||||
return uint32(l.Addr().(*net.TCPAddr).Port), nil
|
||||
}
|
||||
|
||||
// startTestPostgres starts an embedded Postgres on an OS-assigned free port and
|
||||
// returns it with that port and its runtime dir (caller removes it). A port
|
||||
// taken between freePort and Start (shared CI host) is retried with a new one.
|
||||
func startTestPostgres(name string) (*embeddedpostgres.EmbeddedPostgres, uint32, string, error) {
|
||||
rt := filepath.Join(os.TempDir(), name)
|
||||
var lastErr error
|
||||
for range 3 {
|
||||
port, err := freePort()
|
||||
if err != nil {
|
||||
return nil, 0, rt, err
|
||||
}
|
||||
pg := embeddedpostgres.NewDatabase(
|
||||
embeddedpostgres.DefaultConfig().
|
||||
Port(port).
|
||||
RuntimePath(rt).
|
||||
DataPath(filepath.Join(rt, "data")).
|
||||
BinariesPath(filepath.Join(rt, "bin")).
|
||||
CachePath(filepath.Join(os.TempDir(), "tapir-epg-cache")),
|
||||
)
|
||||
if lastErr = pg.Start(); lastErr == nil {
|
||||
return pg, port, rt, nil
|
||||
}
|
||||
lastErr = fmt.Errorf("start on port %d: %w", port, lastErr)
|
||||
}
|
||||
return nil, 0, rt, lastErr
|
||||
}
|
||||
|
||||
// uuids — fixed so tests are deterministic. user_id/video_id are UUID columns.
|
||||
const (
|
||||
userA = "11111111-1111-1111-1111-111111111111"
|
||||
|
||||
Reference in New Issue
Block a user