From 2fc206950f1e5f1d43a6859730cc93893b454944 Mon Sep 17 00:00:00 2001 From: fyui001 Date: Fri, 6 Nov 2026 12:06:30 +0900 Subject: [PATCH] =?UTF-8?q?PR=20=E3=81=AE=E3=81=9F=E3=81=B3=E3=81=AB=20kin?= =?UTF-8?q?d=20=E3=81=A7=E3=83=81=E3=83=A3=E3=83=BC=E3=83=88=E3=82=92?= =?UTF-8?q?=E8=B5=B7=E5=8B=95=E3=81=99=E3=82=8B=E6=A4=9C=E6=9F=BB=E3=81=A8?= =?UTF-8?q?=E3=80=81v=20=E3=81=AE=E3=82=BF=E3=82=B0=E3=81=A7=E3=83=81?= =?UTF-8?q?=E3=83=A3=E3=83=BC=E3=83=88=E3=82=92=20OCI=20=E3=81=AB=E5=87=BA?= =?UTF-8?q?=E3=81=97=E3=81=A6=20GitHub=20Release=20=E3=82=92=E4=BD=9C?= =?UTF-8?q?=E3=82=8B=20CI=20=E3=82=92=E8=B6=B3=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- .github/workflows/check.yml | 17 ++++++ .github/workflows/release.yml | 101 ++++++++++++++++++++++++++++++++ README.md | 1 + charts/pyxis/ci/kind-sizes.yaml | 14 +++++ scripts/check-kind.sh | 89 ++++++++++++++++++++++++++++ scripts/release-notes.sh | 51 ++++++++++++++++ 6 files changed, 273 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 charts/pyxis/ci/kind-sizes.yaml create mode 100755 scripts/check-kind.sh create mode 100755 scripts/release-notes.sh diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index abfce14..0e686df 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -89,6 +89,23 @@ jobs: if: always() run: docker compose down -v -t 60 + kind-up: + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@v7 + - uses: azure/setup-helm@v4 + with: + version: v3.19.0 + - uses: helm/kind-action@v1.15.2 + with: + version: v0.31.0 + cluster_name: pyxis-check + - name: On kind, the chart with a synthetic tuner, the bundled database and no GPU comes up, app and vela answer, and app subscribes to the driver + run: scripts/check-kind.sh + env: + PYXIS_KIND_KEEP: 1 + chart: runs-on: ubuntu-latest env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..06b38ed --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,101 @@ +name: Release + +on: + push: + tags: ['v*'] + +env: + HELM_VERSION: v3.19.0 + CHART_REGISTRY: oci://ghcr.io/newworldorg/charts + +jobs: + version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + prerelease: ${{ steps.version.outputs.prerelease }} + previous: ${{ steps.version.outputs.previous }} + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: azure/setup-helm@v4 + with: + version: ${{ env.HELM_VERSION }} + - name: The tag names the version Chart.yaml declares, and appVersion is the same number + id: version + run: | + version=$(sed -n 's/^version: *//p' charts/pyxis/Chart.yaml) + app_version=$(sed -n 's/^appVersion: *//p' charts/pyxis/Chart.yaml) + if [ "${GITHUB_REF_NAME}" != "v${version}" ]; then + echo "the tag ${GITHUB_REF_NAME} is not v${version}, the version Chart.yaml declares" >&2 + exit 1 + fi + if [ "${app_version}" != "${version}" ]; then + echo "appVersion ${app_version} is not the chart version ${version}" >&2 + exit 1 + fi + case "${version}" in + *-*) prerelease=yes ;; + *) prerelease=no ;; + esac + previous=$(git tag --list 'v*' --sort=-v:refname | grep -vx "${GITHUB_REF_NAME}" | head -n 1 || true) + echo "version=${version}" >> "${GITHUB_OUTPUT}" + echo "prerelease=${prerelease}" >> "${GITHUB_OUTPUT}" + echo "previous=${previous}" >> "${GITHUB_OUTPUT}" + - name: The images in compose.yml, values.yaml and Chart.yaml are the same versions + run: scripts/check-versions.sh + - name: No chart of this version is in the registry yet + run: | + if helm show chart "${CHART_REGISTRY}/pyxis" --version "${{ steps.version.outputs.version }}" >/dev/null 2>&1; then + echo "pyxis ${{ steps.version.outputs.version }} is already in ${CHART_REGISTRY}" >&2 + exit 1 + fi + + chart: + needs: version + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v7 + - uses: azure/setup-helm@v4 + with: + version: ${{ env.HELM_VERSION }} + - name: helm lint with every set of values under ci/ + run: | + for values in charts/pyxis/ci/*-values.yaml; do + helm lint --strict charts/pyxis -f "${values}" + done + - name: Package and push the chart to the registry + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io --username "${{ github.actor }}" --password-stdin + helm package charts/pyxis --destination "${RUNNER_TEMP}" + helm push "${RUNNER_TEMP}/pyxis-${{ needs.version.outputs.version }}.tgz" "${CHART_REGISTRY}" + - name: The pushed chart can be read back by version + run: helm show chart "${CHART_REGISTRY}/pyxis" --version "${{ needs.version.outputs.version }}" + + github-release: + needs: [version, chart] + runs-on: ubuntu-latest + permissions: + contents: write + env: + GH_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: A GitHub Release for the tag, with the images this version pins and whether the driver changes + run: | + if gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1; then + echo "the release ${GITHUB_REF_NAME} is already there and is left as it is." + exit 0 + fi + scripts/release-notes.sh "${{ needs.version.outputs.previous }}" > "${RUNNER_TEMP}/notes.md" + flags="" + if [ "${{ needs.version.outputs.prerelease }}" = yes ]; then + flags="--prerelease" + fi + gh release create "${GITHUB_REF_NAME}" --verify-tag --title "${GITHUB_REF_NAME}" --notes-file "${RUNNER_TEMP}/notes.md" ${flags} diff --git a/README.md b/README.md index d870cae..7511ae4 100644 --- a/README.md +++ b/README.md @@ -459,6 +459,7 @@ docker compose logs driver イメージの版は `compose.yml` と `charts/pyxis/values.yaml` に数字で書いてあり、`latest` は使わない。\ 2 か所が同じ版であることは CI が確かめる。\ +PR のたびに compose は Docker で、チャートは kind で起動し、画面と API が答えることも CI が見る。\ Pyxis の版は、その組で起動・録画・再生を確かめてから付ける。 ## ライセンス diff --git a/charts/pyxis/ci/kind-sizes.yaml b/charts/pyxis/ci/kind-sizes.yaml new file mode 100644 index 0000000..af963fc --- /dev/null +++ b/charts/pyxis/ci/kind-sizes.yaml @@ -0,0 +1,14 @@ +# kind での起動の試験で default-values.yaml に重ねる値。名前を *-values.yaml にしないのは、chart の job が単体で lint と描画をする組と区別するため。置き場を小さくし、そのほかは既定のまま +persistence: + recordings: + size: 1Gi + encodes: + size: 1Gi + thumbnails: + size: 256Mi + captions: + size: 256Mi + keys: + size: 64Mi + db: + size: 1Gi diff --git a/scripts/check-kind.sh b/scripts/check-kind.sh new file mode 100755 index 0000000..01bb822 --- /dev/null +++ b/scripts/check-kind.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# 今の kubectl の context (kind) に、既定の値と ci/kind-sizes.yaml でチャートを入れ、 +# app の /api/health と vela の /login が 200 を返し、app が driver の口 (/events) につないだことを確かめる。 +# 終わったらリリースと名前空間を消す +set -euo pipefail + +cd "$(dirname "$0")/.." + +namespace=${PYXIS_KIND_NAMESPACE:-pyxis-check} +release=carina +app_port=${PYXIS_KIND_APP_PORT:-18080} +vela_port=${PYXIS_KIND_VELA_PORT:-13000} +forwards=() + +cleanup() { + for pid in "${forwards[@]}"; do + kill "$pid" 2>/dev/null || true + done + if [ "${PYXIS_KIND_KEEP:-}" != 1 ]; then + helm uninstall "$release" -n "$namespace" --wait --timeout 5m >/dev/null 2>&1 || true + kubectl delete namespace "$namespace" --wait=false >/dev/null 2>&1 || true + fi +} +trap cleanup EXIT + +describe_failure() { + echo '== pods' + kubectl -n "$namespace" get pods -o wide + echo '== events' + kubectl -n "$namespace" get events --sort-by=.lastTimestamp | tail -n 40 + for part in driver app vela db; do + echo "== logs ${release}-${part}" + kubectl -n "$namespace" logs "deploy/${release}-${part}" --all-containers --tail=60 2>/dev/null \ + || kubectl -n "$namespace" logs "statefulset/${release}-${part}" --all-containers --tail=60 2>/dev/null \ + || true + done +} + +kubectl create namespace "$namespace" --dry-run=client -o yaml | kubectl apply -f - >/dev/null +# driver は privileged で動く +kubectl label namespace "$namespace" pod-security.kubernetes.io/enforce=privileged --overwrite >/dev/null + +if ! helm upgrade --install "$release" charts/pyxis -n "$namespace" \ + -f charts/pyxis/ci/default-values.yaml -f charts/pyxis/ci/kind-sizes.yaml \ + --wait --timeout 10m; then + describe_failure + exit 1 +fi + +kubectl -n "$namespace" port-forward "svc/${release}-app" "${app_port}:8080" >/dev/null 2>&1 & +forwards+=("$!") +kubectl -n "$namespace" port-forward "svc/${release}-vela" "${vela_port}:3000" >/dev/null 2>&1 & +forwards+=("$!") + +health='' +login='' +for _ in $(seq 1 60); do + health=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${app_port}/api/health" || true) + login=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${vela_port}/login" || true) + if [ "$health" = 200 ] && [ "$login" = 200 ]; then + break + fi + sleep 1 +done +echo "health=${health} login=${login}" +if [ "$health" != 200 ] || [ "$login" != 200 ]; then + describe_failure + exit 1 +fi +curl -s "http://127.0.0.1:${app_port}/api/health" +echo + +# app は driver の口につなぐと、まず /events を購読する。driver の要求のログにそれが残る +subscribed='' +for _ in $(seq 1 60); do + if kubectl -n "$namespace" logs "deploy/${release}-driver" --tail=-1 | grep -q 'GET http://driver/events'; then + subscribed=yes + break + fi + sleep 1 +done +if [ -z "$subscribed" ]; then + echo 'app が driver の /events を購読した記録が driver のログに無い' >&2 + describe_failure + exit 1 +fi +echo 'app が driver の /events を購読した' + +kubectl -n "$namespace" get pods -o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[*].ready,PHASE:.status.phase' --no-headers diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh new file mode 100755 index 0000000..7bddbb6 --- /dev/null +++ b/scripts/release-notes.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# GitHub Release の本文を書く。この版が固定するイメージの表と、前の版から driver が替わるかどうか。 +# 引数は前の版のタグ (無ければ空) +set -euo pipefail + +cd "$(dirname "$0")/.." + +previous=${1:-} + +image_of() { + local ref=$1 + local part=$2 + if [ -z "$ref" ]; then + return 0 + fi + local image + image=$(git show "${ref}:charts/pyxis/Chart.yaml" | sed -n "s/^ *images\/${part}: *//p") + # v0.1.0 より前の Chart.yaml は driver を images/carina-driver と書いていた + if [ -z "$image" ] && [ "$part" = puppis-driver ]; then + image=$(git show "${ref}:charts/pyxis/Chart.yaml" | sed -n 's/^ *images\/carina-driver: *//p') + fi + printf '%s' "$image" +} + +row() { + local label=$1 + local part=$2 + local image + image=$(sed -n "s/^ *images\/${part}: *//p" charts/pyxis/Chart.yaml) + printf '| %s | `%s` |\n' "$label" "$image" +} + +echo '| 部品 | イメージ |' +echo '| --- | --- |' +row 'Carina' carina-app +row 'Puppis (driver)' puppis-driver +row 'Vela' vela +row 'PostgreSQL' postgres +echo + +driver_now=$(image_of HEAD puppis-driver) +driver_before=$(image_of "$previous" puppis-driver) +if [ -z "$previous" ]; then + echo '最初の版。' +elif [ "$driver_now" = "$driver_before" ]; then + echo "driver のイメージは ${previous} と同じ。更新しても driver は作り直されず、録画は続く。" +else + echo "driver のイメージが ${previous} の \`${driver_before}\` から替わる。録画中とライブの視聴中を避けて更新する。" +fi +echo +echo "compose は \`git checkout ${GITHUB_REF_NAME:-$(git describe --tags --exact-match 2>/dev/null || echo HEAD)}\` して \`docker compose pull\` と \`docker compose up -d\`、Helm は \`helm upgrade carina oci://ghcr.io/newworldorg/charts/pyxis --version $(sed -n 's/^version: *//p' charts/pyxis/Chart.yaml) -n tv -f values.yaml\`。"