Skip to content

Commit 3cf13af

Browse files
authored
Merge pull request #52 from tamnd/release/v0.1.1
Add the GoReleaser release pipeline
2 parents 5fd6f73 + f79f3ae commit 3cf13af

10 files changed

Lines changed: 402 additions & 43 deletions

File tree

.github/workflows/release.yml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
name: release
2+
3+
# Pushing a version tag turns the GoReleaser config (.goreleaser.yaml) into a
4+
# GitHub release with the archives, Linux packages (deb, rpm, apk), checksums,
5+
# SBOMs and a cosign signature, and pushes the multi-arch container image to
6+
# GHCR. Pull requests and pushes to main run `goreleaser check` only, so a
7+
# config that would fail a real release is caught long before the tag.
8+
9+
on:
10+
push:
11+
branches: [main]
12+
tags: ["v*"]
13+
pull_request:
14+
workflow_dispatch:
15+
16+
permissions:
17+
contents: read
18+
19+
concurrency:
20+
group: release-${{ github.ref }}
21+
cancel-in-progress: true
22+
23+
jobs:
24+
# Fast gate on every PR and push: the config parses and is valid for the
25+
# installed GoReleaser version. No artifacts are built here.
26+
check:
27+
if: github.ref_type != 'tag'
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v5
31+
with:
32+
fetch-depth: 0
33+
- uses: actions/setup-go@v6
34+
with:
35+
go-version-file: go.mod
36+
cache: true
37+
- uses: goreleaser/goreleaser-action@v6
38+
with:
39+
distribution: goreleaser
40+
version: "~> v2"
41+
args: check
42+
43+
# The real release, only on a version tag.
44+
release:
45+
if: github.ref_type == 'tag'
46+
runs-on: ubuntu-latest
47+
permissions:
48+
contents: write # create the GitHub release
49+
packages: write # push the image to ghcr.io
50+
id-token: write # keyless cosign signing
51+
steps:
52+
- uses: actions/checkout@v5
53+
with:
54+
fetch-depth: 0
55+
- uses: actions/setup-go@v6
56+
with:
57+
go-version-file: go.mod
58+
cache: true
59+
60+
# Build and ship the linux/arm64 image from the amd64 runner.
61+
- uses: docker/setup-qemu-action@v3
62+
- uses: docker/setup-buildx-action@v3
63+
- uses: docker/login-action@v3
64+
with:
65+
registry: ghcr.io
66+
username: ${{ github.actor }}
67+
password: ${{ secrets.GITHUB_TOKEN }}
68+
69+
# Tools GoReleaser shells out to for signing and SBOMs.
70+
- uses: sigstore/cosign-installer@v3
71+
- uses: anchore/sbom-action/download-syft@v0
72+
73+
- uses: goreleaser/goreleaser-action@v6
74+
with:
75+
distribution: goreleaser
76+
version: "~> v2"
77+
args: release --clean
78+
env:
79+
# Creating the release and pushing the GHCR image use the built-in
80+
# token. The package-manager publish steps each read their own secret;
81+
# any that is unset leaves that manager skipped (the artifact is still
82+
# produced), so the release never fails for a tap that is not set up.
83+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
84+
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
85+
SCOOP_BUCKET_GITHUB_TOKEN: ${{ secrets.SCOOP_BUCKET_GITHUB_TOKEN }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
/dbrest
33
/cmd/dbrest/dbrest
44

5+
# GoReleaser build output
6+
dist/
7+
58
# Local databases
69
*.sqlite
710
*.sqlite-shm

.goreleaser.yaml

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# GoReleaser turns one tag push into everything a user might install from: raw
2+
# archives, Linux packages (deb, rpm, apk), a multi-arch container image, and
3+
# entries for the package managers (Homebrew, Scoop). `git tag vX.Y.Z && git
4+
# push --tags` fans out to all of them through .github/workflows/release.yml.
5+
#
6+
# Publish steps that push to a repository we do not own yet (the Homebrew tap,
7+
# the Scoop bucket) self-disable when their token is absent. A release with no
8+
# extra secrets still produces every downloadable artifact and the container
9+
# image; each manager lights up the moment its repository and token exist.
10+
version: 2
11+
12+
project_name: dbrest
13+
14+
before:
15+
# Only fetch modules; never `go mod tidy` during a release, the tree is kept
16+
# tidy in CI.
17+
hooks:
18+
- go mod download
19+
20+
builds:
21+
- id: dbrest
22+
binary: dbrest
23+
main: ./cmd/dbrest
24+
env:
25+
- CGO_ENABLED=0
26+
flags:
27+
- -trimpath
28+
ldflags:
29+
- -s -w
30+
- -X main.version={{ .Version }}
31+
- -X main.commit={{ .ShortCommit }}
32+
- -X main.date={{ .CommitDate }}
33+
mod_timestamp: "{{ .CommitTimestamp }}"
34+
targets:
35+
- linux_amd64
36+
- linux_arm64
37+
- linux_arm_7
38+
- linux_386
39+
- darwin_amd64
40+
- darwin_arm64
41+
- windows_amd64
42+
- windows_arm64
43+
- freebsd_amd64
44+
- freebsd_arm64
45+
46+
archives:
47+
# tar.gz everywhere except a zip on Windows.
48+
- id: default
49+
name_template: "dbrest_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}"
50+
format_overrides:
51+
- goos: windows
52+
formats: [zip]
53+
files:
54+
- LICENSE
55+
- README.md
56+
57+
nfpms:
58+
# One nfpm definition emits the deb, rpm, and apk for every Linux build. The
59+
# package is the binary and its license; the database it serves is whatever
60+
# the operator points db-uri at, so there are no package dependencies.
61+
- id: linux-packages
62+
package_name: dbrest
63+
file_name_template: "{{ .ConventionalFileName }}"
64+
vendor: tamnd
65+
homepage: https://github.com/tamnd/dbrest
66+
maintainer: Duc-Tam Nguyen <tamnd87@gmail.com>
67+
description: A PostgREST-compatible REST API for any database.
68+
license: Apache-2.0
69+
formats:
70+
- deb
71+
- rpm
72+
- apk
73+
bindir: /usr/bin
74+
section: utils
75+
contents:
76+
- src: ./LICENSE
77+
dst: /usr/share/doc/dbrest/LICENSE
78+
79+
dockers_v2:
80+
# One multi-platform image built with buildx. GoReleaser stages the prebuilt
81+
# binaries under per-platform directories in the build context and the
82+
# Dockerfile copies the right one through $TARGETPLATFORM.
83+
- images:
84+
- ghcr.io/tamnd/dbrest
85+
tags:
86+
- "{{ .Version }}"
87+
- latest
88+
dockerfile: Dockerfile
89+
platforms:
90+
- linux/amd64
91+
- linux/arm64
92+
labels:
93+
org.opencontainers.image.title: "{{ .ProjectName }}"
94+
org.opencontainers.image.description: "A PostgREST-compatible REST API for any database"
95+
org.opencontainers.image.url: "https://github.com/tamnd/dbrest"
96+
org.opencontainers.image.source: "https://github.com/tamnd/dbrest"
97+
org.opencontainers.image.version: "{{ .Version }}"
98+
org.opencontainers.image.revision: "{{ .FullCommit }}"
99+
org.opencontainers.image.licenses: "Apache-2.0"
100+
101+
homebrew_casks:
102+
# Homebrew cask pushed to the tap repository. Self-disables until
103+
# HOMEBREW_TAP_GITHUB_TOKEN (a PAT with write to tamnd/homebrew-tap) is set,
104+
# so a tokenless release still writes the cask into dist for inspection.
105+
- name: dbrest
106+
repository:
107+
owner: tamnd
108+
name: homebrew-tap
109+
token: '{{ envOrDefault "HOMEBREW_TAP_GITHUB_TOKEN" "" }}'
110+
directory: Casks
111+
homepage: https://github.com/tamnd/dbrest
112+
description: A PostgREST-compatible REST API for any database
113+
skip_upload: '{{ if envOrDefault "HOMEBREW_TAP_GITHUB_TOKEN" "" }}false{{ else }}true{{ end }}'
114+
commit_author:
115+
name: Duc-Tam Nguyen
116+
email: tamnd87@gmail.com
117+
118+
scoops:
119+
# Scoop manifest for Windows, pushed to the bucket repository.
120+
- repository:
121+
owner: tamnd
122+
name: scoop-bucket
123+
token: '{{ envOrDefault "SCOOP_BUCKET_GITHUB_TOKEN" "" }}'
124+
homepage: https://github.com/tamnd/dbrest
125+
description: A PostgREST-compatible REST API for any database
126+
license: Apache-2.0
127+
skip_upload: '{{ if envOrDefault "SCOOP_BUCKET_GITHUB_TOKEN" "" }}false{{ else }}true{{ end }}'
128+
commit_author:
129+
name: Duc-Tam Nguyen
130+
email: tamnd87@gmail.com
131+
132+
checksum:
133+
name_template: "checksums.txt"
134+
algorithm: sha256
135+
136+
sboms:
137+
# A CycloneDX SBOM per archive via syft; the release workflow installs it.
138+
- id: archive
139+
artifacts: archive
140+
141+
signs:
142+
# Keyless cosign signature over the checksum file. It runs only on a real
143+
# release in CI, where the workflow grants the OIDC token cosign needs.
144+
- cmd: cosign
145+
certificate: "${artifact}.pem"
146+
args:
147+
- sign-blob
148+
- "--output-certificate=${certificate}"
149+
- "--output-signature=${signature}"
150+
- "${artifact}"
151+
- "--yes"
152+
artifacts: checksum
153+
output: true
154+
155+
docker_signs:
156+
- cmd: cosign
157+
artifacts: manifests
158+
args:
159+
- sign
160+
- "${artifact}@${digest}"
161+
- "--yes"
162+
163+
changelog:
164+
sort: asc
165+
use: github
166+
filters:
167+
exclude:
168+
- "^docs:"
169+
- "^test:"
170+
- "^chore:"
171+
- "^ci:"
172+
- Merge pull request
173+
- Merge branch
174+
groups:
175+
- title: Features
176+
regexp: '^.*?feat(\(.+\))??!?:.+$'
177+
order: 0
178+
- title: Fixes
179+
regexp: '^.*?fix(\(.+\))??!?:.+$'
180+
order: 1
181+
- title: Other
182+
order: 999
183+
184+
release:
185+
github:
186+
owner: tamnd
187+
name: dbrest
188+
draft: false
189+
prerelease: auto

Dockerfile

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
1-
# Build the dbrest binary in a Go 1.26 builder, then copy it into a minimal
2-
# Alpine image. The binary is statically linked (CGO_ENABLED=0) so it runs on
3-
# any Linux base image without a matching libc.
4-
FROM docker.io/library/golang:1.26-alpine AS builder
5-
WORKDIR /src
6-
COPY go.mod go.sum ./
7-
RUN go mod download
8-
COPY . .
9-
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /bin/dbrest ./cmd/dbrest
10-
11-
FROM docker.io/library/alpine:3.21
12-
COPY --from=builder /bin/dbrest /usr/local/bin/dbrest
13-
EXPOSE 3001
14-
ENTRYPOINT ["/usr/local/bin/dbrest"]
1+
# Consumed by GoReleaser: it copies the already cross-compiled binary out of the
2+
# build context rather than compiling, so the image build is fast and uses the
3+
# same static binary every other artifact ships.
4+
#
5+
# GoReleaser builds one multi-platform image with buildx and stages each
6+
# platform's binary under a $TARGETPLATFORM directory (e.g. linux/amd64/) in the
7+
# build context, so the COPY line selects the right one through the automatic
8+
# TARGETPLATFORM build arg.
9+
FROM alpine:3.21
10+
11+
ARG TARGETPLATFORM
12+
13+
# ca-certificates for TLS to the database; tzdata for sane timestamps.
14+
RUN apk add --no-cache ca-certificates tzdata \
15+
&& adduser -D -H -u 10001 dbrest
16+
17+
COPY $TARGETPLATFORM/dbrest /usr/bin/dbrest
18+
19+
USER dbrest
20+
21+
# 3000 is the API; 3001 is the admin server (/live, /ready, /metrics) when it is
22+
# enabled. Configure the server with a mounted config file or the DBREST_*
23+
# environment, for example:
24+
#
25+
# docker run -p 3000:3000 \
26+
# -e DBREST_DB_BACKEND=postgres \
27+
# -e DBREST_DB_URI="postgres://web@db/app" \
28+
# ghcr.io/tamnd/dbrest
29+
EXPOSE 3000 3001
30+
31+
ENTRYPOINT ["/usr/bin/dbrest"]

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,25 @@ Early, and built subsystem by subsystem against a complete design spec. What wor
4444

4545
The capability model, the backend SPI, and the error envelope are in place. The PostgreSQL dialect and its version-computed capabilities have landed (`backend/postgres`), the reference oracle the conformance harness diffs against. The MySQL/MariaDB dialect has landed too (`backend/mysql`), the first real divergence from the oracle: an explicit IS NULL sort key for NULL placement, a no-conflict-target upsert with a no-op ignore, restricted CAST targets, `REGEXP_LIKE`, and MATCH/AGAINST boolean-mode full text. The SQL Server (T-SQL) dialect has landed as well (`backend/sqlserver`), the quirkiest on syntax and the closest to the oracle on the security model: bracket-quoted identifiers, named `@pN` placeholders, OFFSET/FETCH paging that injects an ORDER BY when the client gave none, a CASE NULL sort key, OUTPUT in place of RETURNING, a multi-statement upsert that the data plane drives, and CONTAINS/FREETEXT full text, with native roles, RLS, and a session-context store. Each driver data plane (Execute and introspection over a live server) is a follow-on slice, since it needs a running database to test. The MongoDB backend has landed too (`backend/mongo`), and it is the one engine that does not use the SQL compiler: it lowers a filter to a `$match` query document, a read to a `$match`/`$sort`/`$skip`/`$limit`/`$project` pipeline, casts to `$convert`, and NULLS placement to an `$addFields` sort key, with the array and range operators Unsupported and the security model emulated app-side. Its live driver data plane (`$lookup`/`$graphLookup` embedding, writes, sampling-based introspection) is the follow-on slice. Each backend joins the conformance harness by adding its fixture and a CI job, with no harness changes.
4646

47+
## Install
48+
49+
Every release ships prebuilt binaries, Linux packages, and a container image. Grab the archive for your platform from the [releases page](https://github.com/tamnd/dbrest/releases), or pull the image:
50+
51+
```sh
52+
docker run -p 3000:3000 \
53+
-e DBREST_DB_BACKEND=sqlite \
54+
-e DBREST_DB_URI='file:/data/example.sqlite' \
55+
ghcr.io/tamnd/dbrest
56+
```
57+
58+
On Debian or Red Hat, install the `.deb` or `.rpm` from the release. With the Go toolchain:
59+
60+
```sh
61+
go install github.com/tamnd/dbrest/cmd/dbrest@latest
62+
```
63+
64+
The Homebrew tap and Scoop bucket come online as those repositories are set up. Each release also carries a checksums file, a CycloneDX SBOM per archive, and a keyless cosign signature.
65+
4766
## Quick start
4867

4968
Write a config file naming the backend and the database:

cmd/dbrest/cli.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,37 @@ func resolveConfigPath(flagPath string, args []string) (string, error) {
2828
return args[0], nil
2929
}
3030

31-
// versionString is the module version when built with one, "dev" otherwise.
31+
// Build metadata. The release pipeline stamps these with -ldflags -X; an
32+
// unstamped build (go build, go run) leaves the defaults and falls back to the
33+
// module version recorded in the build info.
34+
var (
35+
version = "dev"
36+
commit = "none"
37+
date = "unknown"
38+
)
39+
40+
// versionString is the stamped release version, the module version when built
41+
// from a checkout that carries one, or "dev".
3242
func versionString() string {
43+
if version != "dev" {
44+
return version
45+
}
3346
if bi, ok := debug.ReadBuildInfo(); ok && bi.Main.Version != "" && bi.Main.Version != "(devel)" {
3447
return bi.Main.Version
3548
}
3649
return "dev"
3750
}
3851

52+
// versionLine is the full --version line. It adds the commit and build date
53+
// when the binary was stamped by the release pipeline.
54+
func versionLine() string {
55+
v := "dbrest " + versionString()
56+
if commit != "none" || date != "unknown" {
57+
v += fmt.Sprintf(" (commit %s, built %s)", commit, date)
58+
}
59+
return v
60+
}
61+
3962
// probeReady asks a running instance's admin server whether it is ready, the
4063
// --ready verb orchestrators use as a health command. A non-200 answer or an
4164
// unreachable admin server is an error, which main turns into exit status 1.

0 commit comments

Comments
 (0)