Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.gen.go -diff linguist-generated=true
*.gen_test.go -diff linguist-generated=true
*.gen.json -diff linguist-generated=true
**/mocks/** -diff linguist-generated=true
14 changes: 14 additions & 0 deletions .github/scripts/compute_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,33 @@

REPOSITORY_ROOT = Path(__file__).resolve().parents[2]
MODULES = (
"buildinfo",
"codexapp",
"config",
"debugserver",
"di",
"filesystem",
"grpcclient",
"grpcserver",
"grpczap",
"health",
"healthgrpc",
"healthotel",
"healthserver",
"healthzap",
"kafka",
"kafkaproto",
"kafkazap",
"kafkaoutbox",
"kafkaoutboxzap",
"lifecycle",
"log",
"oapivalidator",
"oapivalidatorjwt",
"oidcsession",
"oidcsessionredis",
"postgresdb",
"retry",
"sqlitedb",
"telemetry",
"txmanager",
Expand Down
24 changes: 24 additions & 0 deletions .github/scripts/tests/test_compute_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,30 @@ def completed(stdout: str = "", returncode: int = 0, stderr: str = "") -> subpro


class ReleaseVersionTests(unittest.TestCase):
def test_supports_buildinfo_module(self) -> None:
previous, next_tag = compute_release("buildinfo", "minor", lambda _: completed())
self.assertEqual("", previous)
self.assertEqual("buildinfo/v0.1.0", next_tag)

def test_supports_oidc_session_redis_module(self) -> None:
previous, next_tag = compute_release("oidcsessionredis", "minor", lambda _: completed())
self.assertEqual("", previous)
self.assertEqual("oidcsessionredis/v0.1.0", next_tag)

def test_supports_grpc_modules(self) -> None:
for module in ("grpcclient", "grpcserver", "grpczap", "healthgrpc"):
with self.subTest(module=module):
previous, next_tag = compute_release(module, "minor", lambda _: completed())
self.assertEqual("", previous)
self.assertEqual(f"{module}/v0.1.0", next_tag)

def test_supports_kafka_modules(self) -> None:
for module in ("kafka", "kafkaproto", "kafkazap", "kafkaoutbox", "kafkaoutboxzap"):
with self.subTest(module=module):
previous, next_tag = compute_release(module, "minor", lambda _: completed())
self.assertEqual("", previous)
self.assertEqual(f"{module}/v0.1.0", next_tag)

def test_initial_minor_release_is_v0_1_0(self) -> None:
previous, next_tag = compute_release("health", "minor", lambda _: completed())
self.assertEqual("", previous)
Expand Down
91 changes: 88 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ on:
description: Run PostgreSQL integration tests regardless of changed paths.
type: boolean
default: false
run_redis_integration:
description: Run Redis session integration tests regardless of changed paths.
type: boolean
default: false
run_kafkaoutbox_integration:
description: Run Kafka outbox integration tests regardless of changed paths.
type: boolean
default: false

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
Expand All @@ -30,11 +38,13 @@ jobs:
outputs:
go_ci: ${{ steps.filter.outputs.go_ci }}
postgres: ${{ steps.filter.outputs.postgres }}
redis: ${{ steps.filter.outputs.redis }}
kafkaoutbox: ${{ steps.filter.outputs.kafkaoutbox }}
steps:
- uses: actions/checkout@v6
- name: Detect changed paths
id: filter
if: ${{ !inputs.force_go_ci && !inputs.run_postgres_integration }}
if: ${{ !inputs.force_go_ci && !inputs.run_postgres_integration && !inputs.run_redis_integration && !inputs.run_kafkaoutbox_integration }}
uses: dorny/paths-filter@v4
with:
filters: |
Expand All @@ -55,6 +65,21 @@ jobs:
- 'go.work'
- 'go.work.sum'
- '.github/workflows/ci.yml'
redis:
- 'oidcsession/**'
- 'oidcsessionredis/**'
- 'go.work'
- 'go.work.sum'
- '.github/workflows/ci.yml'
kafkaoutbox:
- 'kafkaoutbox/**'
- 'kafka/**'
- 'postgresdb/**'
- 'txmanager/**'
- 'retry/**'
- 'go.work'
- 'go.work.sum'
- '.github/workflows/ci.yml'

quality:
needs: changes
Expand Down Expand Up @@ -117,18 +142,78 @@ jobs:
- name: Run PostgreSQL integration tests
run: mise run postgresdb:test-integration

redis-integration:
needs: changes
if: inputs.run_redis_integration || needs.changes.outputs.redis == 'true'
runs-on: ubuntu-latest
env:
MISE_TASK_RUN_AUTO_INSTALL: "false"
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
install_args: go
- name: Resolve Go cache
id: go-cache
run: |
echo "build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT"
echo "modules=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT"
echo "version=$(go env GOVERSION)" >> "$GITHUB_OUTPUT"
- name: Restore Go caches
uses: actions/cache@v6
with:
path: |
${{ steps.go-cache.outputs.build }}
${{ steps.go-cache.outputs.modules }}
key: redis-go-${{ runner.os }}-${{ runner.arch }}-${{ steps.go-cache.outputs.version }}-${{ hashFiles('go.work', 'go.work.sum', '**/go.mod', '**/go.sum') }}
restore-keys: |
redis-go-${{ runner.os }}-${{ runner.arch }}-${{ steps.go-cache.outputs.version }}-
- name: Run Redis session integration tests
run: mise run oidcsessionredis:test-integration

kafkaoutbox-integration:
needs: changes
if: inputs.run_kafkaoutbox_integration || needs.changes.outputs.kafkaoutbox == 'true'
runs-on: ubuntu-latest
env:
MISE_TASK_RUN_AUTO_INSTALL: "false"
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
install_args: go
- name: Resolve Go cache
id: go-cache
run: |
echo "build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT"
echo "modules=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT"
echo "version=$(go env GOVERSION)" >> "$GITHUB_OUTPUT"
- name: Restore Go caches
uses: actions/cache@v6
with:
path: |
${{ steps.go-cache.outputs.build }}
${{ steps.go-cache.outputs.modules }}
key: kafkaoutbox-go-${{ runner.os }}-${{ runner.arch }}-${{ steps.go-cache.outputs.version }}-${{ hashFiles('go.work', 'go.work.sum', '**/go.mod', '**/go.sum') }}
restore-keys: |
kafkaoutbox-go-${{ runner.os }}-${{ runner.arch }}-${{ steps.go-cache.outputs.version }}-
- name: Run Kafka outbox integration tests
run: mise run kafkaoutbox:test-integration

gate:
if: always()
needs: [changes, quality, postgres-integration]
needs: [changes, quality, postgres-integration, redis-integration, kafkaoutbox-integration]
runs-on: ubuntu-latest
steps:
- name: Verify CI result
env:
CHANGES_RESULT: ${{ needs.changes.result }}
QUALITY_RESULT: ${{ needs.quality.result }}
POSTGRES_RESULT: ${{ needs.postgres-integration.result }}
REDIS_RESULT: ${{ needs.redis-integration.result }}
KAFKAOUTBOX_RESULT: ${{ needs.kafkaoutbox-integration.result }}
run: |
for result in "$CHANGES_RESULT" "$QUALITY_RESULT" "$POSTGRES_RESULT"; do
for result in "$CHANGES_RESULT" "$QUALITY_RESULT" "$POSTGRES_RESULT" "$REDIS_RESULT" "$KAFKAOUTBOX_RESULT"; do
case "$result" in
success|skipped) ;;
*) exit 1 ;;
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/preview-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
description: Module to preview.
required: true
type: choice
options: [codexapp, config, debugserver, di, filesystem, health, healthotel, healthserver, healthzap, lifecycle, log, oapivalidator, postgresdb, sqlitedb, telemetry, txmanager]
options: [buildinfo, codexapp, config, debugserver, di, filesystem, grpcclient, grpcserver, grpczap, health, healthgrpc, healthotel, healthserver, healthzap, kafka, kafkaproto, kafkazap, kafkaoutbox, kafkaoutboxzap, lifecycle, log, oapivalidator, oapivalidatorjwt, oidcsession, oidcsessionredis, postgresdb, retry, sqlitedb, telemetry, txmanager]
bump:
description: Stable semantic-version increment.
required: true
Expand All @@ -31,6 +31,14 @@ jobs:
fetch-depth: 0
fetch-tags: true
- uses: jdx/mise-action@v4
- name: Verify module outside the workspace
run: |
set -euo pipefail
cd "$MODULE"
GOWORK=off go mod tidy -diff
GOWORK=off go mod download
GOWORK=off go test -race ./...
git diff --exit-code -- go.mod go.sum
- name: Compute release
id: release
run: |
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
description: Module to release.
required: true
type: choice
options: [codexapp, config, debugserver, di, filesystem, health, healthotel, healthserver, healthzap, lifecycle, log, oapivalidator, postgresdb, sqlitedb, telemetry, txmanager]
options: [buildinfo, codexapp, config, debugserver, di, filesystem, grpcclient, grpcserver, grpczap, health, healthgrpc, healthotel, healthserver, healthzap, kafka, kafkaproto, kafkazap, kafkaoutbox, kafkaoutboxzap, lifecycle, log, oapivalidator, oapivalidatorjwt, oidcsession, oidcsessionredis, postgresdb, retry, sqlitedb, telemetry, txmanager]
bump:
description: Stable semantic-version increment.
required: true
Expand Down Expand Up @@ -39,6 +39,8 @@ jobs:
with:
force_go_ci: true
run_postgres_integration: ${{ inputs.module == 'postgresdb' || inputs.module == 'txmanager' }}
run_redis_integration: ${{ inputs.module == 'oidcsessionredis' }}
run_kafkaoutbox_integration: ${{ inputs.module == 'kafkaoutbox' }}

publish:
needs: validate
Expand All @@ -63,9 +65,12 @@ jobs:
echo "previous_tag=$previous_tag" >> "$GITHUB_OUTPUT"
- name: Verify module outside the workspace
run: |
set -euo pipefail
cd "$MODULE"
GOWORK=off go mod tidy -diff
GOWORK=off go mod download
GOWORK=off go test -race ./...
git diff --exit-code -- go.mod go.sum
- name: Generate release notes
env:
NEXT_TAG: ${{ steps.release.outputs.next_tag }}
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ bin/
*.so
*.dylib

.idea/

# Go test and coverage artifacts
*.test
*.out
Expand Down
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ go get github.com/devctllabs/go-libs/<module>@latest
| [`config`](config) | Ordered, composable configuration loaders for defaults, files, dotenv data, and environment variables. |
| [`di`](di) | A small, type-safe dependency container with explicit resource ownership and shutdown. |
| [`lifecycle`](lifecycle) | Coordination for long-running tasks and graceful shutdown. |
| [`retry`](retry) | Explicit context-aware retry loops and capped exponential backoff. |

### Operations and observability

| Module | Description |
| --- | --- |
| [`buildinfo`](buildinfo) | Build metadata embedded in the current Go executable. |
| [`debugserver`](debugserver) | A standalone HTTP server for Go pprof endpoints. |
| [`log`](log) | Production JSON zap logger construction without global logger state. |
| [`telemetry`](telemetry) | Instance-owned OpenTelemetry trace and metric providers for Go services. |
Expand All @@ -37,20 +39,47 @@ go get github.com/devctllabs/go-libs/<module>@latest
| Module | Description |
| --- | --- |
| [`health`](health) | Transport-neutral liveness and readiness probes. |
| [`healthgrpc`](healthgrpc) | Standard gRPC Health service backed by transport-neutral probes. |
| [`healthotel`](healthotel) | OpenTelemetry metrics for health check observations. |
| [`healthserver`](healthserver) | OpenAPI-generated Echo endpoints for liveness and readiness probes. |
| [`healthzap`](healthzap) | Structured zap logging for health check failures and recoveries. |

### gRPC

| Module | Description |
| --- | --- |
| [`grpcclient`](grpcclient) | Explicit gRPC client connections with file-backed TLS, interceptor chains, and opt-in OpenTelemetry. |
| [`grpcserver`](grpcserver) | Application-owned gRPC runtime with validation, panic recovery, reflection, TLS, and graceful shutdown. |
| [`grpczap`](grpczap) | Completion and recovered-panic logging adapters for zap. |

### Messaging

| Module | Description |
| --- | --- |
| [`kafka`](kafka) | Typed franz-go producer and batching consumer runtimes with JSON codecs, retry, reject/DLQ policy, and OpenTelemetry. |
| [`kafkaproto`](kafkaproto) | Protobuf encoders and fresh-message decoders for Kafka values. |
| [`kafkazap`](kafkazap) | Structured zap logging for Kafka consumer retries and dispositions. |
| [`kafkaoutbox`](kafkaoutbox) | PostgreSQL transactional outbox with polling virtual shards or Debezium CDC delivery. |
| [`kafkaoutboxzap`](kafkaoutboxzap) | Structured zap logging for outbox retries, fencing, and topology changes. |

### Data and infrastructure

| Module | Description |
| --- | --- |
| [`filesystem`](filesystem) | Rooted filesystem operations that compose with the standard `io/fs` package. |
| [`oapivalidator`](oapivalidator) | OpenAPI request validation middleware for Echo. |
| [`postgresdb`](postgresdb) | Instrumented pgx reader and writer pools for PostgreSQL. |
| [`sqlitedb`](sqlitedb) | Instrumented SQLite reader and writer endpoints. |
| [`txmanager`](txmanager) | Shared transaction boundaries for services and database adapters. |

### Identity and API security

| Module | Description |
| --- | --- |
| [`oapivalidator`](oapivalidator) | OpenAPI request validation and authentication middleware for Echo. |
| [`oapivalidatorjwt`](oapivalidatorjwt) | JWT bearer and cookie authenticator for `oapivalidator`. |
| [`oidcsession`](oidcsession) | OIDC provider lifecycle, encrypted login state, and browser session HTTP flows. |
| [`oidcsessionredis`](oidcsessionredis) | Redis-backed opaque refresh sessions with encrypted provider tokens. |

### Codex integration

| Module | Description |
Expand All @@ -72,6 +101,8 @@ Run the PostgreSQL integration suite separately when changing `postgresdb` or `t

```sh
mise run postgresdb:test-integration
mise run oidcsessionredis:test-integration
mise run kafkaoutbox:test-integration
```

## Releases
Expand Down
13 changes: 10 additions & 3 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,19 @@ Protect `main`, require the `CI / gate` and `Commit checks / commitlint` checks,

## Bootstrap order

Release `health` and `txmanager` before the modules that depend on them:
Release shared modules before the modules that depend on them:

- `health` before `healthotel`, `healthserver`, and `healthzap`.
- `grpcserver` before `grpczap`.
- `health` before `healthgrpc`, `healthotel`, `healthserver`, and `healthzap`.
- `txmanager` before `postgresdb` and `sqlitedb`.
- `oapivalidator` before `oapivalidatorjwt`.
- `retry` before `oidcsession`, and `oidcsession` before `oidcsessionredis`.
- `retry` before `kafka`, and `kafka` before `kafkaproto` and `kafkazap`.
- `kafka`, `postgresdb`, and `retry` before `kafkaoutbox`, then `kafkaoutbox` before `kafkaoutboxzap`.

Workspace replacements make local monorepo development possible, but the release workflow tests the selected module with `GOWORK=off`. Therefore a dependent module cannot be published until its declared internal dependency exists publicly.
The workspace `use` directives provide local package sources. Versioned internal requirements that do not have public tags yet are centralized as `go.work` replacements; publishable `go.mod` files do not contain local paths. Preview and release both run `go mod tidy -diff`, download dependencies, and run race-enabled tests with `GOWORK=off`, then verify that `go.mod` and `go.sum` stayed unchanged.

Changes to a base module and its dependants may land in one pull request because normal CI uses the workspace. Release them in the order above. A dependent preview or release intentionally fails until the base tag exists and its checksum updates have been committed.

## Preview and publish

Expand Down
Loading