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
176 changes: 175 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,180 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
ci:
uses: codefly-dev/core/.github/workflows/go-service-ci.yml@main
needs: image
permissions:
contents: read
uses: codefly-dev/core/.github/workflows/go-service-ci.yml@25e267bc5b7e346ef6b8439c9a032f115c4795a6

image:
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262

- name: Resolve runtime image
id: runtime
shell: bash
run: |
set -euo pipefail
name=$(jq -er '.name' runtime-image.json)
tag=$(jq -er '.tag' runtime-image.json)
digest=$(jq -er '.digest' runtime-image.json)
echo "name=$name" >> "$GITHUB_OUTPUT"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "digest=$digest" >> "$GITHUB_OUTPUT"
echo "reference=$name@$digest" >> "$GITHUB_OUTPUT"
echo "tag_reference=$name:$tag" >> "$GITHUB_OUTPUT"

- name: Set up Go
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff
with:
cache: false
go-version-file: go.mod

- name: Run unit tests
run: go test ./... -skip '^TestCreateToRunDocker$'

- name: Build runtime image
run: docker build --build-arg SOURCE_DATE_EPOCH=0 --tag service-postgres:test .

- name: Smoke test runtime image
shell: bash
run: |
set -euo pipefail

cleanup() {
docker rm -f service-postgres-root service-postgres-nonroot >/dev/null 2>&1 || true
}
trap cleanup EXIT

smoke() {
local container_name="$1"
shift
docker run --detach --name "$container_name" "$@" \
--env POSTGRES_PASSWORD=test service-postgres:test >/dev/null

local ready=false
for _ in $(seq 1 60); do
local state
local ready_count
state=$(docker inspect --format '{{.State.Status}}' "$container_name")
ready_count=$(docker logs "$container_name" 2>&1 |
grep -c 'database system is ready to accept connections' || true)
if [[ "$ready_count" -ge 2 ]] &&
docker exec "$container_name" pg_isready -U postgres >/dev/null 2>&1; then
ready=true
break
fi
if [[ "$state" != running ]]; then
break
fi
sleep 1
done

if [[ "$ready" != true ]]; then
docker logs "$container_name"
return 1
fi

docker exec "$container_name" psql \
-v ON_ERROR_STOP=1 -U postgres -d postgres \
-c "CREATE EXTENSION vector" \
-c "SELECT round(('[1,2,3]'::vector <-> '[4,5,6]'::vector)::numeric, 6)"
docker rm -f "$container_name" >/dev/null
}

smoke service-postgres-root
smoke service-postgres-nonroot \
--user 70:70 \
--tmpfs /var/lib/postgresql/data:uid=70,gid=70

- name: Log in to GitHub Container Registry
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130

- if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
with:
driver-opts: image=moby/buildkit@sha256:2f5adac4ecd194d9f8c10b7b5d7bceb5186853db1b26e5abd3a657af0b7e26ec

- name: Publish runtime image candidate
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
id: candidate
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8
with:
build-args: SOURCE_DATE_EPOCH=0
context: .
outputs: type=image,name=${{ steps.runtime.outputs.name }},push-by-digest=true,name-canonical=true,push=true,rewrite-timestamp=true
platforms: linux/amd64,linux/arm64
provenance: false
sbom: false

- name: Verify runtime image candidate
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
ACTUAL_DIGEST: ${{ steps.candidate.outputs.digest }}
EXPECTED_DIGEST: ${{ steps.runtime.outputs.digest }}
run: |
set -euo pipefail
if [[ "$ACTUAL_DIGEST" != "$EXPECTED_DIGEST" ]]; then
echo "built digest $ACTUAL_DIGEST does not match runtime-image.json $EXPECTED_DIGEST" >&2
exit 1
fi

- name: Verify published runtime image
env:
RUNTIME_IMAGE: ${{ steps.runtime.outputs.reference }}
run: |
set -euo pipefail
anonymous_docker_config=$(mktemp -d)
trap 'rm -rf -- "$anonymous_docker_config"' EXIT
manifest=$(docker --config "$anonymous_docker_config" manifest inspect "$RUNTIME_IMAGE")
jq -e '
[.manifests[].platform | "\(.os)/\(.architecture)"] | sort ==
["linux/amd64", "linux/arm64"]
' <<< "$manifest"
docker --config "$anonymous_docker_config" pull "$RUNTIME_IMAGE"

- name: Scan published runtime image
env:
RUNTIME_IMAGE: ${{ steps.runtime.outputs.reference }}
run: |
docker save --output /tmp/service-postgres-image.tar "$RUNTIME_IMAGE"
mkdir -p /tmp/trivy-cache
docker run --rm \
--volume /tmp/trivy-cache:/root/.cache/trivy \
--volume /tmp/service-postgres-image.tar:/image.tar:ro \
aquasec/trivy@sha256:cffe3f5161a47a6823fbd23d985795b3ed72a4c806da4c4df16266c02accdd6f \
image --quiet --format table --severity HIGH,CRITICAL \
--exit-code 1 --input /image.tar

- name: Tag verified runtime image
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
EXPECTED_DIGEST: ${{ steps.runtime.outputs.digest }}
RUNTIME_IMAGE: ${{ steps.runtime.outputs.reference }}
RUNTIME_TAG: ${{ steps.runtime.outputs.tag_reference }}
run: |
set -euo pipefail
docker buildx imagetools create --tag "$RUNTIME_TAG" "$RUNTIME_IMAGE"
tagged_digest=$(docker buildx imagetools inspect "$RUNTIME_TAG" |
awk '$1 == "Digest:" { print $2; exit }')
if [[ "$tagged_digest" != "$EXPECTED_DIGEST" ]]; then
echo "tagged digest $tagged_digest does not match $EXPECTED_DIGEST" >&2
exit 1
fi
59 changes: 54 additions & 5 deletions .github/workflows/releaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,68 @@ on:
required: true
type: string

permissions:
contents: read

jobs:
image:
if: github.event_name == 'push'
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262

- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
with:
driver-opts: image=moby/buildkit@sha256:2f5adac4ecd194d9f8c10b7b5d7bceb5186853db1b26e5abd3a657af0b7e26ec

- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Publish release image tags
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
name=$(jq -er '.name' runtime-image.json)
tag=$(jq -er '.tag' runtime-image.json)
digest=$(jq -er '.digest' runtime-image.json)
RUNTIME_IMAGE="$name@$digest"
docker buildx imagetools create \
--tag "$name:$RELEASE_TAG" \
--tag "$name:$tag" \
"$RUNTIME_IMAGE"
for published_tag in "$name:$RELEASE_TAG" "$name:$tag"; do
published_digest=$(docker buildx imagetools inspect "$published_tag" |
awk '$1 == "Digest:" { print $2; exit }')
if [[ "$published_digest" != "$digest" ]]; then
echo "$published_tag resolves to $published_digest, want $digest" >&2
exit 1
fi
done

release:
if: github.event_name == 'push'
uses: codefly-dev/core/.github/workflows/go-service-release.yml@main
needs: image
permissions:
contents: read
uses: codefly-dev/core/.github/workflows/go-service-release.yml@25e267bc5b7e346ef6b8439c9a032f115c4795a6
secrets: inherit

backfill:
if: github.event_name == 'workflow_dispatch'
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
- name: Check out release source
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
fetch-depth: 0
ref: ${{ inputs.tag }}
Expand All @@ -32,7 +81,7 @@ jobs:
env:
RELEASE_TAG: ${{ inputs.tag }}
run: .github/scripts/release-backfill.sh validate-tag release-source
- uses: actions/setup-go@v5
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff
with:
go-version-file: release-source/go.mod
cache-dependency-path: release-source/go.sum
Expand All @@ -48,7 +97,7 @@ jobs:
env:
GOFLAGS: -modfile=${{ runner.temp }}/release.mod
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a
with:
version: '~> v2'
args: release --clean --skip=publish,announce
Expand Down
42 changes: 42 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# syntax=docker/dockerfile:1.7@sha256:a57df69d0ea827fb7266491f2813635de6f17269be881f696fbfdf2d83dda33e

ARG POSTGRES_IMAGE=postgres:17.10-alpine3.24@sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193
ARG SOURCE_DATE_EPOCH=0

FROM ${POSTGRES_IMAGE} AS pgvector-builder

ARG PGVECTOR_VERSION=0.8.5
ARG PGVECTOR_SHA256=6f88a5cbdde31666f4b6c1a6b75c51dcbeffe58f9a7d2b26e502d5a6e5e14d44

RUN apk add --no-cache \
build-base=0.5-r4 \
clang21=21.1.8-r3 \
llvm21-dev=21.1.8-r1
ADD --checksum=sha256:${PGVECTOR_SHA256} \
https://github.com/pgvector/pgvector/archive/refs/tags/v${PGVECTOR_VERSION}.tar.gz \
/tmp/pgvector.tar.gz
RUN mkdir /tmp/pgvector /tmp/pgvector-install && \
tar -xzf /tmp/pgvector.tar.gz -C /tmp/pgvector --strip-components=1 && \
make -C /tmp/pgvector OPTFLAGS="" && \
make -C /tmp/pgvector DESTDIR=/tmp/pgvector-install install

FROM ${POSTGRES_IMAGE} AS runtime

RUN rm /usr/local/bin/gosu
RUN apk add --no-cache su-exec=0.3-r0 && \
ln -s /sbin/su-exec /usr/local/bin/gosu && \
rm /var/log/apk.log
COPY --from=pgvector-builder /tmp/pgvector-install/ /

LABEL org.opencontainers.image.source="https://github.com/codefly-dev/service-postgres"

ENV LANG=en_US.utf8
ENV PG_MAJOR=17
ENV PG_VERSION=17.10
ENV PGDATA=/var/lib/postgresql/data

VOLUME ["/var/lib/postgresql/data"]
ENTRYPOINT ["docker-entrypoint.sh"]
STOPSIGNAL SIGINT
EXPOSE 5432
CMD ["postgres"]
9 changes: 2 additions & 7 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ func (s *Builder) Sync(ctx context.Context, req *builderv0.SyncRequest) (*builde
return s.Builder.SyncResponse()
}

// Audit scans the postgres image for known CVEs (HIGH/CRITICAL) via
// trivy. The image tag comes from the package-level `image` var
// (postgres:16.1-alpine by default).
// Audit scans the configured postgres image for known HIGH/CRITICAL CVEs.
func (s *Builder) Audit(ctx context.Context, req *builderv0.AuditRequest) (*builderv0.AuditResponse, error) {
defer s.Wool.Catch()
ctx = s.Wool.Inject(ctx)
Expand All @@ -82,10 +80,7 @@ func (s *Builder) SBOM(ctx context.Context, _ *builderv0.SBOMRequest) (*builderv
return s.Builder.SBOMContainer(ctx, s.dockerImage().FullName())
}

// Upgrade reports a tag bump from the current postgres image (e.g.
// 16.1-alpine → 16.4 within major 16; or 17.0 if --major). Persisting
// the new tag is left to the caller — postgres has no lockfile to
// rewrite, the image var lives in the agent code.
// Upgrade reports an available tag bump for the managed postgres image.
func (s *Builder) Upgrade(ctx context.Context, req *builderv0.UpgradeRequest) (*builderv0.UpgradeResponse, error) {
defer s.Wool.Catch()
ctx = s.Wool.Inject(ctx)
Expand Down
Loading
Loading