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
245 changes: 245 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
name: Release

# Manual only. Publishing is a deliberate act: someone chooses the
# moment, from main, on the canonical repository -- never a fork,
# and never as a side effect of a push.
on:
workflow_dispatch:

permissions:
contents: read

# Guarded twice over on every job: workflow_dispatch lets a caller
# pick any ref, and forks carry this file too. Anything but main on
# emfga/cel4postgres skips rather than publishes.
#
# The artifact build is deterministic and instant, so each job
# rebuilds dist/ from the checkout instead of passing files
# between jobs -- fewer moving parts, and nothing to pin.

jobs:
# The plain-scripts channel: the artifact is the initdb script
# for the same pinned image compose uses, and readiness is
# cel.version() answering. The all-in bundle and the core-only
# file are the two shapes that could break independently; each
# extension file is a verbatim copy of a sql/ script CI already
# installs.
smoke:
if: >-
github.repository == 'emfga/cel4postgres' &&
github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Build and install artifacts
run: |
./scripts/build-release.sh
version=$(sed -n \
"s/^VALUES ('\([0-9][0-9.]*\)')$/\1/p" \
sql/000_install.sql)
image=$(sed -n 's/^ *image: \(postgres:[^ ]*\)$/\1/p' \
compose.yaml)
init=/docker-entrypoint-initdb.d/install.sql
for artifact in \
"cel4postgres--$version.sql" \
"cel4postgres-core--$version.sql"
do
echo "== $artifact"
docker run -d --rm --name relcheck \
-e POSTGRES_DB=cel -e POSTGRES_USER=cel \
-e POSTGRES_PASSWORD=pw \
-v "$PWD/dist/$artifact:$init:ro" \
"$image" >/dev/null
got=
for _ in $(seq 60); do
got=$(docker exec relcheck psql -U cel -d cel -tAc \
'SELECT cel.version()' 2>/dev/null) && break
sleep 2
done
docker rm -f relcheck >/dev/null
if [ "$got" != "$version" ]; then
echo "expected $version, got '$got'" >&2
exit 1
fi
done

# The flagship channel: the same dist/ files, wrapped by
# scripts/pgtle-wrap.sh into pgtle.install_extension, installed
# with CREATE EXTENSION cel4postgres, and proven by evaluating
# an expression that needs the variant's environment. Env rows
# exist even in the core-only install, so only an evaluation is
# evidence that an extension's items are really there.
pgtle:
if: >-
github.repository == 'emfga/cel4postgres' &&
github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- variant: all
parts: all
env: standard
expr: 1 + 2
want: '{"v": 3, "@t": "int"}'
- variant: core
parts: core
env: standard
expr: 1 + 2
want: '{"v": 3, "@t": "int"}'
- variant: ext_comprehensions
parts: core ext_comprehensions
env: standard,two_var_comprehensions
expr: '[1, 2, 3].transformList(i, v, v * 2)'
want: >-
{"v": [{"v": 2, "@t": "int"}, {"v": 4, "@t":
"int"}, {"v": 6, "@t": "int"}], "@t": "list"}
- variant: ext_optionals
parts: core ext_optionals
env: standard,optionals
expr: optional.of(1).hasValue()
want: '{"v": true, "@t": "bool"}'
- variant: ext_strings
parts: core ext_strings
env: standard,strings
expr: '"a,b".split(",")'
want: >-
{"v": [{"v": "a", "@t": "string"}, {"v": "b",
"@t": "string"}], "@t": "list"}
- variant: ext_math
parts: core ext_math
env: standard,math
expr: math.least(1, 2)
want: '{"v": 1, "@t": "int"}'
- variant: ext_lists
parts: core ext_lists
env: standard,lists
expr: '[3, 1, 2].sort()'
want: >-
{"v": [{"v": 1, "@t": "int"}, {"v": 2, "@t":
"int"}, {"v": 3, "@t": "int"}], "@t": "list"}
- variant: ext_encoders
parts: core ext_encoders
env: standard,encoders
expr: base64.encode(b"hi")
want: '{"v": "aGk=", "@t": "string"}'
- variant: ext_bindings
parts: core ext_bindings
env: standard,bindings
expr: cel.bind(x, 1, x + x)
want: '{"v": 2, "@t": "int"}'
- variant: ext_network
parts: core ext_network
env: standard,network
expr: isIP("127.0.0.1")
want: '{"v": true, "@t": "bool"}'
steps:
- uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Build pg_tle image
run: |
base=$(sed -n 's/^ *image: \(postgres:[^ ]*\)$/\1/p' \
compose.yaml)
docker build -f docker/pg_tle.Dockerfile \
--build-arg BASE_IMAGE="$base" -t pgtle .

- name: Install through pg_tle and evaluate
env:
PARTS: ${{ matrix.parts }}
CEL_ENV: ${{ matrix.env }}
EXPR: ${{ matrix.expr }}
WANT: ${{ matrix.want }}
run: |
./scripts/build-release.sh
version=$(sed -n \
"s/^VALUES ('\([0-9][0-9.]*\)')$/\1/p" \
sql/000_install.sql)
files=
for p in $PARTS; do
if [ "$p" = all ]; then
files="dist/cel4postgres--$version.sql"
else
files="$files dist/cel4postgres-$p--$version.sql"
fi
done
# shellcheck disable=SC2086
./scripts/pgtle-wrap.sh cel4postgres "$version" \
$files >wrapped.sql
docker run -d --rm --name tle \
-e POSTGRES_DB=cel -e POSTGRES_USER=cel \
-e POSTGRES_PASSWORD=pw \
-v "$PWD/wrapped.sql:/wrapped.sql:ro" \
pgtle -c shared_preload_libraries=pg_tle >/dev/null
for _ in $(seq 60); do
docker exec tle pg_isready -U cel -d cel -q \
2>/dev/null && break
sleep 2
done
docker exec tle psql -U cel -d cel -v ON_ERROR_STOP=1 \
-c 'CREATE EXTENSION pg_tle;' \
-f /wrapped.sql \
-c 'CREATE EXTENSION cel4postgres;'
got=$(docker exec tle psql -U cel -d cel -tAc \
"SELECT extversion FROM pg_extension
WHERE extname = 'cel4postgres'")
if [ "$got" != "$version" ]; then
echo "pg_extension says '$got', not $version" >&2
exit 1
fi
got=$(docker exec tle psql -U cel -d cel -tAc "
SELECT cel.evaluate(\$e\$$EXPR\$e\$, '{}',
'$CEL_ENV')")
docker rm -f tle >/dev/null
if [ "$got" != "$WANT" ]; then
echo "evaluate returned: $got" >&2
echo "expected: $WANT" >&2
exit 1
fi

# The tag does not exist beforehand; gh creates it at the
# released commit, and re-running on an already-released
# version fails here instead of republishing.
publish:
if: >-
github.repository == 'emfga/cel4postgres' &&
github.ref == 'refs/heads/main'
needs: [smoke, pgtle]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
# gh release create pushes the tag and uploads the assets.
contents: write
steps:
- uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Build and publish
env:
GH_TOKEN: ${{ github.token }}
run: |
./scripts/build-release.sh
version=$(sed -n \
"s/^VALUES ('\([0-9][0-9.]*\)')$/\1/p" \
sql/000_install.sql)
notes="Install with: psql -v ON_ERROR_STOP=1"
notes="$notes -f cel4postgres--$version.sql."
notes="$notes Verify downloads against SHA256SUMS."
gh release create "v$version" dist/* \
--target "$GITHUB_SHA" \
--title "cel4postgres $version" \
--notes "$notes"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@

# CI-only corpus checkout location
.cel-expr/
dist/
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,11 @@ done
```

It needs a role that may create the `cel` schema. It does not need
superuser.
superuser. [docs/INSTALL.md](docs/INSTALL.md) is the full guide:
release artifacts and their checksums, self-hosted and AWS
RDS/Aurora instructions — including installing as a real extension
via [pg_tle](https://github.com/aws/pg_tle) — and the grants an
application role needs.

## Scope

Expand Down
2 changes: 1 addition & 1 deletion conformance/envs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package conformance
// default environment quietly gained an extension is not a passing
// file.
//
// macros2 was measured (Phase 0): every one of its 46 cases uses the
// macros2 was measured: every one of its 46 cases uses the
// two-var comprehension macros and none uses optional syntax, so the
// whole file takes two_var_comprehensions and nothing else.
var fileEnvs = map[string]string{
Expand Down
14 changes: 7 additions & 7 deletions conformance/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import (

// string(double) is implemented by cel._double_text, which must match
// Go's %g exactly, because that is what cel-go's string(double) emits
// (common/types/double.go:141, pinned v0.32.0). The workspace ruling
// on I2 committed this test: it fuzzes that claim on every CI run
// rather than trusting a handful of probes -- and its very first run
// proved bare float8::text insufficient (Go switches to scientific
// notation at e+06, Postgres at e+15), which is why the function
// exists. A mismatch here reopens the formatting question with a
// concrete value in hand.
// (common/types/double.go:141, pinned v0.32.0). This test fuzzes
// that claim on every CI run rather than trusting a handful of
// probes -- and its very first run proved bare float8::text
// insufficient (Go switches to scientific notation at e+06,
// Postgres at e+15), which is why the function exists. A mismatch
// here reopens the formatting question with a concrete value in
// hand.
//
// Non-finite values are excluded by design: they never reach the
// Postgres formatter (the evaluator emits +Inf/-Inf/NaN itself from
Expand Down
11 changes: 4 additions & 7 deletions conformance/infra_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// Package conformance holds the suite that measures cel4postgres
// against the cel-spec conformance corpus.
//
// Nothing here reads a .textproto yet. These are the infrastructure
// tests: they fail loudly when the database is missing or the schema
// was never installed, so that a later red conformance run is never
// ambiguous about which of the two went wrong.
// The infrastructure tests: they fail loudly when the database is
// missing or the schema was never installed, so that a red
// conformance run is never ambiguous about which of the two went
// wrong.
package conformance

import (
Expand Down
2 changes: 1 addition & 1 deletion conformance/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ type CaseFailure struct {

// Divergence is a case the two implementations judge differently.
// Since cel4postgres follows the corpus wherever the two disagree
// (the corpus-first ruling), these are almost always cases cel-go
// (docs/CONFORMANCE.md), these are almost always cases cel-go
// itself does not satisfy -- which is exactly what a reader comparing
// the two needs told.
type Divergence struct {
Expand Down
6 changes: 3 additions & 3 deletions conformance/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ func stageErrors(envelope any) ([]any, bool) {
return errs, ok
}

// checkOptions builds the options argument of cel.check (decision 7):
// the case's container and its type_env ident declarations.
// checkOptions builds the options argument of cel.check: the case's
// container and its type_env ident declarations.
func checkOptions(tc *test.SimpleTest) ([]byte, error) {
options := map[string]any{}
if tc.GetContainer() != "" {
Expand Down Expand Up @@ -271,7 +271,7 @@ func activationJSON(tc *test.SimpleTest) ([]byte, error) {
}

// compareResult applies the case's result matcher. A missing matcher
// defaults to value: bool true (measured, workspace doc 01).
// defaults to value: bool true (measured against cel-go v0.32.0).
func compareResult(got any, rawResult []byte, tc *test.SimpleTest) error {
compare := func(want any) error {
if codec.Equal(want, got) {
Expand Down
20 changes: 20 additions & 0 deletions docker/pg_tle.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# The pinned postgres image plus pg_tle, for validating that the
# release artifacts install through the flagship channel
# (CREATE EXTENSION via pgtle.install_extension). Validation-only:
# nothing built here is published.
#
# BASE_IMAGE must match compose.yaml's pinned image; the workflow
# extracts it from there so the two cannot drift.
ARG BASE_IMAGE=postgres:18-alpine
FROM ${BASE_IMAGE}

# Pinned like every other reference: a bump is a deliberate change.
ARG PG_TLE_VERSION=v1.5.2

RUN apk add --no-cache --virtual .build \
build-base git flex bison openssl-dev krb5-dev \
&& git clone --depth 1 --branch "${PG_TLE_VERSION}" \
https://github.com/aws/pg_tle.git /tmp/pg_tle \
&& make -C /tmp/pg_tle install with_llvm=no \
&& rm -rf /tmp/pg_tle \
&& apk del .build
Loading