Skip to content
Closed
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
33 changes: 30 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,17 @@ jobs:
- name: Extract version from release tag
id: version
run: |
VERSION="${{ github.event.release.tag_name }}"
# Support both release event and manual workflow_dispatch
if [ -n "${{ inputs.tag }}" ]; then
VERSION="${{ inputs.tag }}"
else
VERSION="${{ github.event.release.tag_name }}"
fi
VERSION="${VERSION#v}"
if [ -z "$VERSION" ]; then
echo "::error::No version found. Provide a tag via release or workflow_dispatch."
exit 1
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Building version: ${VERSION}"

Expand Down Expand Up @@ -104,13 +113,31 @@ jobs:
sleep 1
done

# Verify extension is loadable
# Verify fresh install
docker exec test-pg psql -U postgres -c "CREATE EXTENSION ulak;"
docker exec test-pg psql -U postgres -c "SELECT * FROM ulak.health_check();"

# Verify protocol support
docker exec test-pg psql -U postgres -c \
"SELECT ulak.validate_endpoint_config('http', '{\"url\": \"https://example.com/webhook\"}'::jsonb);"

echo "PG ${{ matrix.pg }} image verified successfully"
echo "PG ${{ matrix.pg }} fresh install verified"

# Verify upgrade path from previous version (if upgrade scripts exist)
docker exec test-pg psql -U postgres -c "DROP EXTENSION IF EXISTS ulak CASCADE;"
UPGRADE_SCRIPTS=$(docker exec test-pg sh -c "ls /usr/share/postgresql/${{ matrix.pg }}/extension/ulak--*--*.sql 2>/dev/null | wc -l")
if [ "$UPGRADE_SCRIPTS" -gt "0" ]; then
# Find the earliest version that has an upgrade path to current
EARLIEST=$(docker exec test-pg sh -c "ls /usr/share/postgresql/${{ matrix.pg }}/extension/ulak--*.sql" \
| grep -oP 'ulak--\K[0-9]+\.[0-9]+\.[0-9]+(?=\.sql)' \
| sort -V | head -1)
echo "Testing upgrade path from $EARLIEST to current..."
docker exec test-pg psql -U postgres -c "CREATE EXTENSION ulak VERSION '$EARLIEST';"
docker exec test-pg psql -U postgres -c "ALTER EXTENSION ulak UPDATE;"
docker exec test-pg psql -U postgres -c "SELECT * FROM ulak.health_check();"
echo "PG ${{ matrix.pg }} upgrade path verified ($EARLIEST → current)"
else
echo "No upgrade scripts found, skipping upgrade test"
fi

docker stop test-pg
1 change: 1 addition & 0 deletions Dockerfile.publish
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ COPY --from=builder /usr/share/postgresql/${PG_MAJOR}/extension/ulak--*.sql \
# libnats and libhiredis have no stable Debian package names — copy from builder
COPY --from=builder /usr/lib/*/libnats.so* /usr/lib/
COPY --from=builder /usr/lib/*/libhiredis.so* /usr/lib/
COPY --from=builder /usr/lib/*/libhiredis_ssl.so* /usr/lib/

# Auto-load extension via shared_preload_libraries
RUN echo "shared_preload_libraries = 'ulak'" >> /usr/share/postgresql/postgresql.conf.sample
Expand Down
2 changes: 1 addition & 1 deletion META.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"provides": {
"ulak": {
"abstract": "Transactional Outbox Pattern for PostgreSQL",
"file": "sql/ulak--0.0.0.sql",
"file": "sql/ulak--0.0.2.sql",
"docfile": "README.md",
"version": "0.0.2"
}
Expand Down
13 changes: 9 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql version.txt

# Only clean generated install files, NOT future upgrade scripts (--X.Y.Z--A.B.C.sql)
# Also clean protocol dispatcher object files (always, regardless of build flags)
EXTRA_CLEAN = sql/$(EXTENSION)--[0-9].[0-9].[0-9].sql
EXTRA_CLEAN = sql/$(EXTENSION)--[0-9]*.[0-9]*.[0-9]*.sql
EXTRA_CLEAN += src/dispatchers/http/http_security.o src/dispatchers/http/http_security.bc
EXTRA_CLEAN += src/dispatchers/http/http_request.o src/dispatchers/http/http_request.bc
EXTRA_CLEAN += src/dispatchers/http/http_sync.o src/dispatchers/http/http_sync.bc
Expand Down Expand Up @@ -202,15 +202,20 @@ REGRESS = 00_setup 01_schema 02_endpoints_crud 03_endpoints_validation \
15_production_hardening 16_message_lifecycle 17_cloudevents \
18_advanced_operations 19_http_proxy 20_kafka_config 21_redis_config \
22_mqtt_config 23_amqp_config 24_nats_config \
25_metrics 99_cleanup
25_metrics \
26_concurrency_limit 27_send_options_v2 28_purge_endpoint \
29_queue_health 30_schema_validation 31_transform_hooks \
32_notify_config 33_check_archive_default \
99_cleanup

# Test options: use tests/regress/ subdirectory for input/output, dedicated test database
REGRESS_OPTS = --inputdir=tests/regress --outputdir=tests/regress --dbname=ulak_test

# Isolation tests for concurrency scenarios (FOR UPDATE SKIP LOCKED, circuit breaker, ordering)
ISOLATION = skip_locked modulo_partition ordering_key circuit_breaker batch_mark_processing \
circuit_breaker_threshold circuit_breaker_recovery ordering_key_completion \
retry_visibility priority_contention idempotency_conflict dlq_concurrent_redrive
retry_visibility priority_contention idempotency_conflict dlq_concurrent_redrive \
concurrency_limit debounce_conflict
ISOLATION_OPTS = --inputdir=tests/isolation --outputdir=tests/isolation --dbname=ulak_test

# TAP tests for worker lifecycle scenarios (requires IPC::Run Perl module)
Expand Down Expand Up @@ -388,6 +393,7 @@ dist: version-check sql/$(EXTENSION)--$(EXTVERSION).sql
@mkdir -p $(DIST_DIR)
@rsync -a \
--exclude='.git/' \
--exclude='.github/' \
--exclude='dist/' \
--exclude='.DS_Store' \
--exclude='docs/' \
Expand All @@ -398,7 +404,6 @@ dist: version-check sql/$(EXTENSION)--$(EXTVERSION).sql
--exclude='*.so' \
--exclude='*.o' \
./ $(DIST_DIR)/$(DIST_BASENAME)/
@rm -rf $(DIST_DIR)/$(DIST_BASENAME)/.github
@rm -rf $(DIST_DIR)/$(DIST_BASENAME)/docs
@rm -f $(DIST_DIR)/$(DIST_BASENAME)/Dockerfile
@rm -f $(DIST_DIR)/$(DIST_BASENAME)/Dockerfile.publish
Expand Down
16 changes: 12 additions & 4 deletions scripts/distcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ def main() -> int:
forbidden = [
".git/",
".github/",
".claude/",
".DS_Store",
"BLOG_TR.md",
"Dockerfile",
"Dockerfile.publish",
"Doxyfile",
Expand All @@ -35,8 +33,6 @@ def main() -> int:
"results/",
"tmp_check/",
"tmp_check_iso/",
"docs/doxygen/",
"roadmap/",
]

if not archive.exists():
Expand Down Expand Up @@ -64,6 +60,18 @@ def main() -> int:
print(f" - {item}", file=sys.stderr)
return 1

# Check upgrade scripts are included (if they exist in sql/ directory)
upgrade_scripts = sorted(Path("sql").glob("ulak--*--*.sql"))
missing_upgrades = [
s.name for s in upgrade_scripts
if prefix + f"sql/{s.name}" not in names
]
if missing_upgrades:
print("Archive is missing upgrade scripts:", file=sys.stderr)
for item in missing_upgrades:
print(f" - sql/{item}", file=sys.stderr)
return 1

present_forbidden: list[str] = []
for item in forbidden:
needle = prefix + item
Expand Down
27 changes: 27 additions & 0 deletions scripts/release_check.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python3

import json
import re
import sys
from pathlib import Path

Expand All @@ -14,10 +15,16 @@ def extract_control_value(text: str, key: str) -> str | None:
return None


def extract_h_version(text: str) -> str | None:
m = re.search(r'#define\s+ULAK_VERSION\s+"([^"]+)"', text)
return m.group(1) if m else None


def main() -> int:
version = Path("version.txt").read_text().strip()
control = Path("ulak.control").read_text()
meta = json.loads(Path("META.json").read_text())
ulak_h = Path("include/ulak.h").read_text()

errors: list[str] = []
fixed: list[str] = []
Expand All @@ -28,6 +35,12 @@ def main() -> int:
f"ulak.control default_version={control_version!r} does not match version.txt={version!r}"
)

h_version = extract_h_version(ulak_h)
if h_version != version:
errors.append(
f"include/ulak.h ULAK_VERSION={h_version!r} does not match version.txt={version!r}"
)

if meta.get("version") != version:
errors.append(f"META.json version={meta.get('version')!r} does not match version.txt={version!r}")

Expand All @@ -44,6 +57,20 @@ def main() -> int:
Path("META.json").write_text(json.dumps(meta, indent=2) + "\n")
fixed.append(f"Auto-fixed META.json provides.ulak.file to {expected_sql_file}")

# Check upgrade script exists (unless this is the initial version)
initial_version = "0.0.1"
if version != initial_version:
upgrade_scripts = sorted(Path("sql").glob(f"ulak--*--{version}.sql"))
if not upgrade_scripts:
errors.append(
f"No upgrade script found targeting version {version}. "
f"Expected at least one sql/ulak--X.Y.Z--{version}.sql file "
f"for existing users to upgrade via ALTER EXTENSION ulak UPDATE."
)
else:
for script in upgrade_scripts:
print(f"Upgrade script found: {script}")

for msg in fixed:
print(msg)

Expand Down
Loading
Loading