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
29 changes: 14 additions & 15 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
node_modules
dist
*.log
pnpm-debug.log*

.git
.github
.githooks

.DS_Store
.idea
.vscode

.env
.env.example
*
!Dockerfile
!catalog.json
!catalog.schema.json
!src/
!src/grpc/
!src/grpc/*.proto
!src/kafka/
!src/kafka/*.json
!scripts/
!scripts/check.sh
!scripts/registry.sh
!scripts/verify.sh
!scripts/apply.sh
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
SCHEMA_REGISTRY_URL="http://localhost:8081"
SCHEMA_REGISTRY_API_PATH="/apis/registry/v3"
SCHEMA_REGISTRY_CCOMPAT_PATH="/apis/ccompat/v7"
SCHEMA_REGISTRY_CONNECT_TIMEOUT_SECONDS="5"
SCHEMA_REGISTRY_REQUEST_TIMEOUT_SECONDS="30"
149 changes: 149 additions & 0 deletions .github/scripts/packages.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/env bash
set -euo pipefail

: "${GITHUB_TOKEN:?GITHUB_TOKEN is required}"

readonly RETAINED_BUILDS=10
readonly PACKAGE="shared-schemas"
readonly IMAGE="ghcr.io/monadiam/$PACKAGE"
readonly LOCAL_IMAGE="monadiam/shared-schemas:ci"
readonly PACKAGE_API="/orgs/MonadIAM/packages/container/$PACKAGE"
readonly VERSION_PATTERN='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
readonly DIGEST_PATTERN='^sha256:[0-9a-f]{64}$'
readonly SHA_TAG_PATTERN='^sha-[0-9a-f]{40}$'

commit=$(git rev-parse HEAD)
sha_tag="sha-$commit"
versions='[]'
source_digest=''
published_tag=''

load_versions() {
local packages

packages=$(gh api --paginate --slurp \
"/orgs/MonadIAM/packages?package_type=container&per_page=100")

if jq -e --arg name "$PACKAGE" \
'any(.[][]; .name == $name)' <<< "$packages" >/dev/null; then
versions=$(gh api --paginate --slurp \
"$PACKAGE_API/versions?per_page=100" | jq 'add')

source_digest=$(jq -r --arg tag "$sha_tag" '
.[] | select(.metadata.container.tags | index($tag)) | .name
' <<< "$versions")
fi
}

publish_commit() {
local revision

if [[ -n "$source_digest" ]]; then
printf 'Reusing %s:%s\n' "$IMAGE" "$sha_tag"
else
revision=$(docker inspect "$LOCAL_IMAGE" \
--format '{{index .Config.Labels "org.opencontainers.image.revision"}}')

if [[ "$revision" == "$commit" ]]; then
docker tag "$LOCAL_IMAGE" "$IMAGE:$sha_tag"
docker push "$IMAGE:$sha_tag"
else
printf 'Image revision does not match %s\n' "$commit" >&2
exit 1
fi
fi

published_tag="$sha_tag"
}

publish_release() {
local version existing_digest

: "${GITHUB_REF_NAME:?GITHUB_REF_NAME is required}"
version=$(jq -er '.version' package.json)

if [[ "$version" =~ $VERSION_PATTERN &&
"$GITHUB_REF_NAME" == "v$version" &&
"$source_digest" =~ $DIGEST_PATTERN ]]; then
existing_digest=$(jq -r --arg tag "$version" '
.[] | select(.metadata.container.tags | index($tag)) | .name
' <<< "$versions")

if [[ -z "$existing_digest" ]]; then
docker buildx imagetools create --prefer-index=false \
--tag "$IMAGE:$version" "$IMAGE@$source_digest"
elif [[ "$existing_digest" == "$source_digest" ]]; then
printf 'Reusing release %s\n' "$version"
else
printf 'Release %s already points to another digest\n' "$version" >&2
exit 1
fi
else
printf 'Release requires a matching version tag and published SHA image\n' >&2
exit 1
fi

published_tag="$version"
}

cleanup() {
local candidates id

candidates=$(jq -r \
--arg pattern "$SHA_TAG_PATTERN" \
--arg current "$sha_tag" \
--argjson keep "$RETAINED_BUILDS" '
map(select(
.metadata.container.tags |
length > 0 and all(.[]; test($pattern))
))
| sort_by(.created_at) | reverse
| .[$keep:][]
| select(.metadata.container.tags | index($current) == null)
| .id
' <<< "$versions")

if [[ -n "$candidates" ]]; then
while IFS= read -r id; do
gh api --method DELETE "$PACKAGE_API/versions/$id"
printf 'Deleted intermediate image %s\n' "$id"
done <<< "$candidates"
fi
}

verify_publication() {
local digest

digest=$(docker buildx imagetools inspect "$IMAGE:$published_tag" \
--format '{{.Manifest.Digest}}')

if [[ "$digest" =~ $DIGEST_PATTERN &&
( -z "$source_digest" || "$digest" == "$source_digest" ) ]]; then
printf 'Image: %s:%s\nDigest: %s@%s\n' \
"$IMAGE" "$published_tag" "$IMAGE" "$digest" \
| tee -a "$GITHUB_STEP_SUMMARY"
else
printf 'Unexpected digest: %s\n' "$digest" >&2
exit 1
fi
}

case "${1:?Usage: packages.sh sha|release|cleanup}" in
sha|release)
: "${GITHUB_STEP_SUMMARY:?GITHUB_STEP_SUMMARY is required}"
load_versions

if [[ "$1" == "sha" ]]; then
publish_commit
else
publish_release
fi

verify_publication ;;
cleanup)
load_versions
cleanup ;;
*)
printf 'Unknown operation: %s\n' "$1" >&2
exit 1 ;;
esac
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ jobs:
- name: Build package
run: pnpm run build

- name: Build schema publisher image
run: make image IMAGE=monadiam/shared-schemas:ci VCS_REF="$GITHUB_SHA"

- name: Check schema publisher image
run: docker run --rm --read-only --network none monadiam/shared-schemas:ci scripts/check.sh

ci:
name: CI passed
if: ${{ always() }}
Expand Down
49 changes: 49 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Publish schema image

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

defaults:
run:
shell: bash

jobs:
publish:
name: Publish schema image
if: ${{ github.ref == 'refs/heads/main' }}
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
packages: write
concurrency:
group: shared-schemas-package
cancel-in-progress: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Build image from merged commit
run: make image IMAGE=monadiam/shared-schemas:ci VCS_REF="$GITHUB_SHA"

- name: Log in to GHCR
run: printf '%s' "$GITHUB_TOKEN" | docker login ghcr.io --username "$GITHUB_ACTOR" --password-stdin

- name: Publish commit image
run: make image-publish

- name: Prune intermediate images
run: make image-cleanup

- name: Log out of GHCR
if: ${{ always() }}
run: docker logout ghcr.io
43 changes: 43 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Release schema image

on:
push:
tags: ["v*"]

permissions:
contents: read
packages: write

concurrency:
group: shared-schemas-package
cancel-in-progress: false

defaults:
run:
shell: bash

jobs:
release:
runs-on: ubuntu-24.04
timeout-minutes: 10
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Check out release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Require a commit from main
run: git merge-base --is-ancestor HEAD origin/main

- name: Log in to GHCR
run: printf '%s' "$GITHUB_TOKEN" | docker login ghcr.io --username "$GITHUB_ACTOR" --password-stdin

- name: Tag published image with package version
run: make image-release

- name: Log out of GHCR
if: ${{ always() }}
run: docker logout ghcr.io
25 changes: 25 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM alpine:3.23

ARG VCS_REF
RUN : "${VCS_REF:?VCS_REF build argument is required}" \
&& apk add --no-cache bash ca-certificates curl jq

LABEL org.opencontainers.image.source="https://github.com/MonadIAM/shared" \
org.opencontainers.image.revision="${VCS_REF}" \
org.opencontainers.image.description="MonadIAM schema catalog publisher"

WORKDIR /opt/shared
ENV SCHEMA_REGISTRY_API_PATH=/apis/registry/v3 \
SCHEMA_REGISTRY_CCOMPAT_PATH=/apis/ccompat/v7 \
SCHEMA_REGISTRY_CONNECT_TIMEOUT_SECONDS=5 \
SCHEMA_REGISTRY_REQUEST_TIMEOUT_SECONDS=30
COPY catalog.json catalog.schema.json ./
COPY src/grpc ./src/grpc
COPY src/kafka ./src/kafka
COPY scripts/check.sh scripts/registry.sh scripts/verify.sh scripts/apply.sh ./scripts/
RUN chmod -R a+rX /opt/shared

USER 65532:65532
RUN ./scripts/check.sh
ENTRYPOINT ["/bin/bash"]
CMD ["scripts/apply.sh"]
14 changes: 13 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.PHONY: build lint lint-fix tsc knip secrets-check lsp env check apply verify graphify graphify-rebuild graphify-html
.PHONY: build lint lint-fix tsc knip secrets-check lsp env check apply verify image image-publish image-release image-cleanup graphify graphify-rebuild graphify-html

GRAPHIFY ?= graphify
IMAGE ?= monadiam/shared-schemas:local
VCS_REF ?= $(shell git rev-parse HEAD)

# Development
build:
Expand All @@ -27,6 +29,16 @@ apply:
pnpm run apply
verify:
pnpm run verify
image:
docker build --build-arg VCS_REF="$(VCS_REF)" --tag "$(IMAGE)" .

# GHCR (GitHub Actions)
image-publish:
bash .github/scripts/packages.sh sha
image-release:
bash .github/scripts/packages.sh release
image-cleanup:
bash .github/scripts/packages.sh cleanup

# Local code graph
graphify:
Expand Down
Loading
Loading