diff --git a/.github/workflows/publish-asterinas-kata-image.yml b/.github/workflows/publish-asterinas-kata-image.yml index 41848ee647..75f198eb79 100644 --- a/.github/workflows/publish-asterinas-kata-image.yml +++ b/.github/workflows/publish-asterinas-kata-image.yml @@ -39,6 +39,7 @@ jobs: docker_image_version: ${{ steps.resolve.outputs.docker_image_version }} kata_static_tarball_release_repo: ${{ steps.resolve.outputs.kata_static_tarball_release_repo }} kata_static_tarball_url: ${{ steps.resolve.outputs.kata_static_tarball_url }} + kata_static_tarball_sha256: ${{ steps.resolve.outputs.kata_static_tarball_sha256 }} should_push: ${{ steps.resolve.outputs.should_push }} steps: - name: Resolve Publish Inputs @@ -67,6 +68,27 @@ jobs: image_repository="${WORKFLOW_IMAGE_REPOSITORY:-${DEFAULT_IMAGE_REPOSITORY}}" kata_static_tarball_release_repo="${WORKFLOW_RELEASE_REPO:-${GITHUB_REPOSITORY}}" kata_static_tarball_url="${WORKFLOW_STATIC_TARBALL_URL:-}" + kata_static_tarball_sha256= + if [ -z "${kata_static_tarball_url}" ]; then + kata_static_tarball_url="$( + gh api "repos/${kata_static_tarball_release_repo}/releases/latest" --jq ' + .assets[] + | select(.name | test("^kata-static-.*-asterinas-amd64\\.tar\\.zst$")) + | .browser_download_url + ' | head -n 1 + )" + fi + test -n "${kata_static_tarball_url}" + kata_static_tarball_name="${kata_static_tarball_url##*/}" + kata_static_tarball_sha256="$( + gh api "repos/${kata_static_tarball_release_repo}/releases/latest" --jq ' + .assets[] + | select(.name | test("^kata-static-.*-asterinas-amd64\\.SHA256SUMS$")) + | .browser_download_url + ' | head -n 1 | + xargs curl -fsSL | + awk -v asset_name="${kata_static_tarball_name}" '$2 == asset_name || $2 ~ ("/" asset_name "$") { print $1; exit }' + )" should_push=false if [ -n "${DOCKERHUB_USERNAME}" ] && [ -n "${DOCKERHUB_TOKEN}" ]; then @@ -84,6 +106,7 @@ jobs: echo "docker_image_version=${docker_image_version}" >> "${GITHUB_OUTPUT}" echo "kata_static_tarball_release_repo=${kata_static_tarball_release_repo}" >> "${GITHUB_OUTPUT}" echo "kata_static_tarball_url=${kata_static_tarball_url}" >> "${GITHUB_OUTPUT}" + echo "kata_static_tarball_sha256=${kata_static_tarball_sha256}" >> "${GITHUB_OUTPUT}" echo "should_push=${should_push}" >> "${GITHUB_OUTPUT}" { @@ -99,6 +122,7 @@ jobs: else echo "- Explicit Kata tarball URL: not set" fi + echo "- Kata tarball SHA256: \`${kata_static_tarball_sha256}\`" echo "- Push enabled: \`${should_push}\`" } >> "${GITHUB_STEP_SUMMARY}" @@ -145,6 +169,7 @@ jobs: ASTERINAS_BASE_IMAGE=${{ env.ASTERINAS_BASE_IMAGE }} KATA_STATIC_TARBALL_RELEASE_REPO=${{ needs.resolve-upstream-asterinas-image.outputs.kata_static_tarball_release_repo }} KATA_STATIC_TARBALL_URL=${{ needs.resolve-upstream-asterinas-image.outputs.kata_static_tarball_url }} + KATA_STATIC_TARBALL_SHA256=${{ needs.resolve-upstream-asterinas-image.outputs.kata_static_tarball_sha256 }} ASTERINAS_RELEASE_VERSION=${{ needs.resolve-upstream-asterinas-image.outputs.asterinas_version }} ASTERINAS_DOCKER_IMAGE_VERSION=${{ needs.resolve-upstream-asterinas-image.outputs.docker_image_version }} diff --git a/.github/workflows/release-asterinas-kata-bundle.yml b/.github/workflows/release-asterinas-kata-bundle.yml index 55e8cccc15..4472bf3c88 100644 --- a/.github/workflows/release-asterinas-kata-bundle.yml +++ b/.github/workflows/release-asterinas-kata-bundle.yml @@ -59,6 +59,8 @@ jobs: build-asterinas-kernel: name: Build Asterinas kernel artifact runs-on: ubuntu-22.04 + outputs: + asterinas_commit: ${{ steps.asterinas_commit.outputs.value }} container: image: ${{ inputs.asterinas_builder_image || 'asterinas/asterinas:0.17.1-20260319' }} steps: @@ -69,6 +71,13 @@ jobs: ref: ${{ env.ASTERINAS_REF }} path: asterinas + - name: Record Asterinas Commit + id: asterinas_commit + shell: bash + run: | + set -euo pipefail + echo "value=$(git -C asterinas rev-parse HEAD)" >> "${GITHUB_OUTPUT}" + - name: Build qemu-direct Kernels shell: bash run: | @@ -153,6 +162,7 @@ jobs: ASTERINAS_TDX_KERNEL: ${{ github.workspace }}/build/asterinas-kernel/aster-kernel-osdk-bin.qemu_elf-tdx ASTERINAS_REPOSITORY: ${{ env.ASTERINAS_REPOSITORY }} ASTERINAS_REF: ${{ env.ASTERINAS_REF }} + ASTERINAS_COMMIT: ${{ needs.build-asterinas-kernel.outputs.asterinas_commit }} ASTERINAS_BUILDER_IMAGE: ${{ env.ASTERINAS_BUILDER_IMAGE }} FORCE_RUNTIME_BUILD: ${{ env.FORCE_RUNTIME_BUILD }} KATA_STATIC_RELEASE_REPOSITORY: ${{ env.KATA_STATIC_RELEASE_REPOSITORY }} diff --git a/.github/workflows/test-asterinas-kata-docs.yml b/.github/workflows/test-asterinas-kata-docs.yml new file mode 100644 index 0000000000..c5f451182a --- /dev/null +++ b/.github/workflows/test-asterinas-kata-docs.yml @@ -0,0 +1,202 @@ +name: Test | Asterinas Kata Docs + +on: + workflow_dispatch: + push: + branches: + - asterinas + +permissions: + contents: read + +env: + ASTERINAS_REPOSITORY: jjf-dev/asterinas + ASTERINAS_REF: kata-support + +jobs: + resolve-upstream-asterinas-image: + name: Resolve upstream Asterinas image + runs-on: ubuntu-latest + outputs: + asterinas_version: ${{ steps.resolve.outputs.asterinas_version }} + docker_image_version: ${{ steps.resolve.outputs.docker_image_version }} + kata_static_tarball_url: ${{ steps.resolve.outputs.kata_static_tarball_url }} + kata_static_tarball_sha256: ${{ steps.resolve.outputs.kata_static_tarball_sha256 }} + steps: + - name: Resolve Upstream Asterinas Image Metadata + id: resolve + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + + asterinas_version="$( + gh api repos/asterinas/asterinas/contents/VERSION?ref=main --jq .content | + base64 -d | tr -d '\n' + )" + docker_image_version="$( + gh api repos/asterinas/asterinas/contents/DOCKER_IMAGE_VERSION?ref=main --jq .content | + base64 -d | tr -d '\n' + )" + kata_static_tarball_url="$( + gh api "repos/${GITHUB_REPOSITORY}/releases/latest" --jq ' + .assets[] + | select(.name | test("^kata-static-.*-asterinas-amd64\\.tar\\.zst$")) + | .browser_download_url + ' | head -n 1 + )" + test -n "${kata_static_tarball_url}" + kata_static_tarball_name="${kata_static_tarball_url##*/}" + kata_static_tarball_sha256="$( + gh api "repos/${GITHUB_REPOSITORY}/releases/latest" --jq ' + .assets[] + | select(.name | test("^kata-static-.*-asterinas-amd64\\.SHA256SUMS$")) + | .browser_download_url + ' | head -n 1 | + xargs curl -fsSL | + awk -v asset_name="${kata_static_tarball_name}" '$2 == asset_name || $2 ~ ("/" asset_name "$") { print $1; exit }' + )" + test -n "${asterinas_version}" + test -n "${docker_image_version}" + test -n "${kata_static_tarball_sha256}" + + echo "asterinas_version=${asterinas_version}" >> "${GITHUB_OUTPUT}" + echo "docker_image_version=${docker_image_version}" >> "${GITHUB_OUTPUT}" + echo "kata_static_tarball_url=${kata_static_tarball_url}" >> "${GITHUB_OUTPUT}" + echo "kata_static_tarball_sha256=${kata_static_tarball_sha256}" >> "${GITHUB_OUTPUT}" + { + echo '## Resolved upstream Asterinas image' + echo + echo "- \`VERSION\`: \`${asterinas_version}\`" + echo "- \`DOCKER_IMAGE_VERSION\`: \`${docker_image_version}\`" + echo "- Base image: \`asterinas/asterinas:${docker_image_version}\`" + echo "- Kata static tarball: \`${kata_static_tarball_url}\`" + } >> "${GITHUB_STEP_SUMMARY}" + + test-documented-end-user-flow: + name: Test documented end-user flow + needs: + - resolve-upstream-asterinas-image + runs-on: ubuntu-latest + timeout-minutes: 120 + env: + KATA_DOC_LOG_DIR: /tmp/asterinas-kata-doc-logs/end-user + steps: + - uses: actions/checkout@v4 + + - name: Check Host Device Visibility + shell: bash + run: | + set -euxo pipefail + ls -l /dev/kvm /dev/vhost-net /dev/vhost-vsock + ls -l /dev/vsock || true + + - name: Install pexpect if Needed + shell: bash + run: | + set -euxo pipefail + if ! python3 - <<'PY' + import importlib.util + import sys + sys.exit(0 if importlib.util.find_spec("pexpect") else 1) + PY + then + python3 -m pip install --user pexpect + fi + + - name: Pull Published Asterinas Kata Image + shell: bash + run: | + set -euxo pipefail + docker pull "asterinas/kata:${{ needs.resolve-upstream-asterinas-image.outputs.docker_image_version }}" + + - name: Replay Documented End-User Flow + shell: bash + run: | + set -euxo pipefail + python3 -u tools/kata/interactive_doc_test.py \ + --scenario end-user \ + --kata-image "asterinas/kata:${{ needs.resolve-upstream-asterinas-image.outputs.docker_image_version }}" \ + --log-dir "${KATA_DOC_LOG_DIR}" + + - name: Upload End-User Flow Logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: asterinas-kata-doc-end-user-logs + path: ${{ env.KATA_DOC_LOG_DIR }} + if-no-files-found: ignore + + test-documented-kernel-developer-flow: + name: Test documented kernel-developer flow + needs: + - resolve-upstream-asterinas-image + runs-on: ubuntu-latest + timeout-minutes: 180 + env: + KATA_DOC_LOG_DIR: /tmp/asterinas-kata-doc-logs/kernel-developer + KATA_STATIC_TARBALL_RELEASE_REPO: ${{ github.repository }} + KATA_STATIC_TARBALL_URL: ${{ needs.resolve-upstream-asterinas-image.outputs.kata_static_tarball_url }} + KATA_STATIC_TARBALL_SHA256: ${{ needs.resolve-upstream-asterinas-image.outputs.kata_static_tarball_sha256 }} + steps: + - uses: actions/checkout@v4 + + - name: Check Out Asterinas Source + uses: actions/checkout@v4 + with: + repository: ${{ env.ASTERINAS_REPOSITORY }} + ref: ${{ env.ASTERINAS_REF }} + path: asterinas-src + + - name: Prepare Local Asterinas Tree + shell: bash + run: | + set -euxo pipefail + rm -rf "${HOME}/asterinas" + mkdir -p "${HOME}/asterinas" + rsync -a --delete "${GITHUB_WORKSPACE}/asterinas-src/" "${HOME}/asterinas/" + + - name: Check Host Device Visibility + shell: bash + run: | + set -euxo pipefail + ls -l /dev/kvm /dev/vhost-net /dev/vhost-vsock + ls -l /dev/vsock || true + + - name: Install pexpect if Needed + shell: bash + run: | + set -euxo pipefail + if ! python3 - <<'PY' + import importlib.util + import sys + sys.exit(0 if importlib.util.find_spec("pexpect") else 1) + PY + then + python3 -m pip install --user pexpect + fi + + - name: Pull Published Asterinas Builder Image + shell: bash + run: | + set -euxo pipefail + docker pull "asterinas/asterinas:${{ needs.resolve-upstream-asterinas-image.outputs.docker_image_version }}" + + - name: Replay Documented Kernel-Developer Flow + shell: bash + run: | + set -euxo pipefail + python3 -u tools/kata/interactive_doc_test.py \ + --scenario kernel-developer \ + --asterinas-image "asterinas/asterinas:${{ needs.resolve-upstream-asterinas-image.outputs.docker_image_version }}" \ + --asterinas-dir "${HOME}/asterinas" \ + --log-dir "${KATA_DOC_LOG_DIR}" + + - name: Upload Kernel-Developer Flow Logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: asterinas-kata-doc-kernel-developer-logs + path: ${{ env.KATA_DOC_LOG_DIR }} + if-no-files-found: ignore diff --git a/.github/workflows/test-asterinas-kata.yml b/.github/workflows/test-asterinas-kata.yml index b2dc0f6186..33d443676c 100644 --- a/.github/workflows/test-asterinas-kata.yml +++ b/.github/workflows/test-asterinas-kata.yml @@ -20,6 +20,8 @@ jobs: outputs: asterinas_version: ${{ steps.resolve.outputs.asterinas_version }} docker_image_version: ${{ steps.resolve.outputs.docker_image_version }} + kata_static_tarball_url: ${{ steps.resolve.outputs.kata_static_tarball_url }} + kata_static_tarball_sha256: ${{ steps.resolve.outputs.kata_static_tarball_sha256 }} steps: - name: Resolve Upstream Asterinas Image Metadata id: resolve @@ -37,17 +39,39 @@ jobs: gh api repos/asterinas/asterinas/contents/DOCKER_IMAGE_VERSION?ref=main --jq .content | base64 -d | tr -d '\n' )" + kata_static_tarball_url="$( + gh api "repos/${GITHUB_REPOSITORY}/releases/latest" --jq ' + .assets[] + | select(.name | test("^kata-static-.*-asterinas-amd64\\.tar\\.zst$")) + | .browser_download_url + ' | head -n 1 + )" + test -n "${kata_static_tarball_url}" + kata_static_tarball_name="${kata_static_tarball_url##*/}" + kata_static_tarball_sha256="$( + gh api "repos/${GITHUB_REPOSITORY}/releases/latest" --jq ' + .assets[] + | select(.name | test("^kata-static-.*-asterinas-amd64\\.SHA256SUMS$")) + | .browser_download_url + ' | head -n 1 | + xargs curl -fsSL | + awk -v asset_name="${kata_static_tarball_name}" '$2 == asset_name || $2 ~ ("/" asset_name "$") { print $1; exit }' + )" test -n "${asterinas_version}" test -n "${docker_image_version}" + test -n "${kata_static_tarball_sha256}" echo "asterinas_version=${asterinas_version}" >> "${GITHUB_OUTPUT}" echo "docker_image_version=${docker_image_version}" >> "${GITHUB_OUTPUT}" + echo "kata_static_tarball_url=${kata_static_tarball_url}" >> "${GITHUB_OUTPUT}" + echo "kata_static_tarball_sha256=${kata_static_tarball_sha256}" >> "${GITHUB_OUTPUT}" { echo '## Resolved upstream Asterinas image' echo echo "- \`VERSION\`: \`${asterinas_version}\`" echo "- \`DOCKER_IMAGE_VERSION\`: \`${docker_image_version}\`" echo "- Base image: \`asterinas/asterinas:${docker_image_version}\`" + echo "- Kata static tarball: \`${kata_static_tarball_url}\`" } >> "${GITHUB_STEP_SUMMARY}" build-published-kata-image: @@ -88,6 +112,8 @@ jobs: build-args: | ASTERINAS_BASE_IMAGE=${{ env.ASTERINAS_BASE_IMAGE }} KATA_STATIC_TARBALL_RELEASE_REPO=${{ github.repository }} + KATA_STATIC_TARBALL_URL=${{ needs.resolve-upstream-asterinas-image.outputs.kata_static_tarball_url }} + KATA_STATIC_TARBALL_SHA256=${{ needs.resolve-upstream-asterinas-image.outputs.kata_static_tarball_sha256 }} ASTERINAS_RELEASE_VERSION=${{ needs.resolve-upstream-asterinas-image.outputs.asterinas_version }} ASTERINAS_DOCKER_IMAGE_VERSION=${{ needs.resolve-upstream-asterinas-image.outputs.docker_image_version }} @@ -119,6 +145,8 @@ jobs: KATA_GUEST_KERNEL: ${{ matrix.guest_kernel }} KATA_TEST_IMAGE_LOG_LABEL: ${{ matrix.image_variant == 'source' && 'base image / guest kernel' || 'test image / guest kernel' }} KATA_TEST_IMAGE_LOG_VALUE: ${{ matrix.image_variant == 'source' && format('asterinas/asterinas:{0} / {1}', needs.resolve-upstream-asterinas-image.outputs.docker_image_version, matrix.guest_kernel) || format('asterinas/kata:{0} / {1}', needs.resolve-upstream-asterinas-image.outputs.docker_image_version, matrix.guest_kernel) }} + KATA_STATIC_TARBALL_URL: ${{ needs.resolve-upstream-asterinas-image.outputs.kata_static_tarball_url }} + KATA_STATIC_TARBALL_SHA256: ${{ needs.resolve-upstream-asterinas-image.outputs.kata_static_tarball_sha256 }} defaults: run: shell: bash diff --git a/docs/asterinas-kata-doc-workflow-progress.md b/docs/asterinas-kata-doc-workflow-progress.md new file mode 100644 index 0000000000..278a037a29 --- /dev/null +++ b/docs/asterinas-kata-doc-workflow-progress.md @@ -0,0 +1,68 @@ +# Asterinas Kata Doc Workflow Progress + +## 2026-04-22 Initial implementation notes + +- Requirement focus: + - add a new workflow that follows `docs/doc-in-asterinas-repo.md` + - keep the overall documented flow, but correct commands that are currently off + - test the interactive `docker run -it` and inner `nerdctl run -it` behavior with `pexpect` + - mount the local Asterinas tree from `$HOME/asterinas` for the developer flow + - use local `asterinas/asterinas` and `asterinas/kata` images first, then wire the same logic into CI +- Document drift confirmed and corrected in the implementation plan: + - `docker run --cgroupns none` is invalid; use `--cgroupns host` + - the outer container needs `/dev/vhost-net` and `/dev/vsock` in addition to `/dev/kvm` and `/dev/vhost-vsock` + - helper script names are `tools/kata/kata_env.sh` and `tools/kata/kata_services.sh` + - the developer flow bind mount should be `-v "${ASTERINAS_SRC}:/root/asterinas"` + - the Kata config path should be `/etc/kata-containers/configuration.toml` +- Added `tools/kata/interactive_doc_test.py`: + - uses `pexpect` to drive the documented interactive flow instead of only calling the existing non-interactive smoke helper + - covers the end-user scenario on `asterinas/kata` + - covers the kernel-developer scenario on `asterinas/asterinas` + - validates the local kernel handoff path with `/root/asterinas/target/osdk/aster-kernel-osdk-bin.qemu_elf` +- Added `.github/workflows/test-asterinas-kata-docs.yml`: + - one job replays the documented end-user flow against `asterinas/kata` + - one job replays the documented kernel-developer flow against `asterinas/asterinas` + - both jobs install `pexpect` only when needed and upload transcripts as artifacts +- Local environment findings: + - `pexpect` is already available in the current local environment + - local `asterinas/asterinas:*` and `asterinas/kata:*` images are present + - the local Asterinas source tree exists at `/home/jianfeng/asterinas` + - the local machine exposes `/dev/kvm`, `/dev/vhost-net`, `/dev/vhost-vsock`, and `/dev/vsock` + - local Docker Hub access currently times out during the inner `nerdctl run` +- Local validation completed so far: + - the documented end-user flow passed locally with + `python3 tools/kata/interactive_doc_test.py --scenario end-user --workload-image docker.1ms.run/alpine:latest` + - this successfully covered the interactive outer `docker run -it`, `tools/kata/kata_services.sh start`, inner `nerdctl run -it`, and post-exit `nerdctl rm foo` sequence + - after cleaning the `pexpect` output parsing and passing outer env overrides explicitly, the same end-user replay was run again locally and still passed + - per the latest user instruction, the kernel-developer flow does not need to finish locally; CI is now the authoritative validation path for that scenario +- Local validation policy: + - local testing uses `docker.1ms.run/alpine:latest` as the workload image when Docker Hub is unreachable + - CI must keep using `docker.io/alpine:latest` +- CI wiring note: + - the kernel-developer workflow path exports `KATA_STATIC_TARBALL_RELEASE_REPO=${GITHUB_REPOSITORY}` so `tools/kata/kata_env.sh install` validates this repository's published Asterinas Kata release assets instead of defaulting to upstream `kata-containers/kata-containers` + - the first GitHub dispatch attempt exposed a GitHub expression-validation issue: `runner.temp` is not accepted in this workflow's job-level `env`, so the log directory paths were normalized to plain `/tmp/...` + - the first push run also showed that `/dev/vsock` is not guaranteed on GitHub-hosted runners, while `/dev/kvm`, `/dev/vhost-net`, and `/dev/vhost-vsock` are present; the workflow and `pexpect` driver now treat `/dev/vsock` as optional instead of mandatory + - based on user feedback, the `pexpect` driver now streams child output to stdout and prints explicit phase markers before long-running commands such as `kata_env.sh install`, `nerdctl run`, and `make kernel` + - the guest-entry helper now fails fast when `nerdctl run -it ...` falls back to the outer shell instead of opening the inner guest shell, so proxy/image failures no longer look like a generic hang + - CI logs showed the real long pole inside `tools/kata/kata_env.sh install`: the static tarball path unpacked into a temporary directory and then copied a second 4.7 GiB tree into `/opt`; this has now been collapsed into a direct `tar --zstd -xf ... -C / opt/kata` install path, with explicit progress messages around the install phases + - the next CI log sample exposed a follow-on bug in that optimization: the tarball did not contain a directly addressable `opt/kata` member for selective extraction, so the install now unpacks the verified tarball directly at `/` instead + - the latest CI round also exposed an unrelated but real stability issue in `Publish | Asterinas Kata Image`: the Docker build resolved the latest release URL from inside the container and hit unauthenticated GitHub API rate limits, so the workflows now resolve the tarball URL and SHA256 up front and pass them into the container explicitly + - after that pre-resolution change, the next push showed a format mismatch in the release `SHA256SUMS`: the checksum file records the tarball with a full path suffix instead of only the basename, so the workflow-side SHA lookup now accepts either an exact basename match or a trailing `/${asset_name}` + - configuration comparison result: before the local-kernel override, both the end-user flow and the kernel-developer flow start Kata from the same default config target, `/opt/kata/share/defaults/kata-containers/configuration.toml -> configuration-asterinas.toml` + - on interactive failures, `tools/kata/interactive_doc_test.py` now also dumps `/tmp/kata-console.log`, `/tmp/console.log`, `/tmp/kata-qemu-serial.log`, `/tmp/qemu-serial.log`, `/tmp/containerd.log`, and `/tmp/kata-syslog.log` so QEMU-side failures are visible immediately + - to keep the interactive developer replay within the expected post-install budget, the CI job now prebuilds the mounted `$HOME/asterinas` kernel once before invoking the `pexpect` driver; the driver still runs the documented `make kernel BOOT_METHOD=qemu-direct` command inside the container, but that command should now hit an already-built tree and return quickly + - a deeper replay hang was then traced to the Python driver itself rather than Kata: `DockerShell.run()` sent the target command, `status=$?`, and the exit-marker `printf` as three separate queued lines, which is fragile for long-running interactive commands. The driver now wraps each command and exit-marker emission into one shell line so it can no longer stall waiting for a marker that was never executed + - the newest transcript showed the remaining long pole after `kata_env.sh install`: the interactive `make kernel BOOT_METHOD=qemu-direct` reinitialized `/nix`, cargo, and rustup state inside the developer container. The workflow now prebuilds with shared cache mounts and passes the same cache mounts into the interactive container so the documented in-container `make kernel` can reuse the same build state + - the first cache-sharing attempt repeated the earlier workflow-expression mistake by putting `runner.temp` into job-level `env`; the cache directories are now normalized to plain `/tmp/asterinas-kernel-cache/...` + - a follow-up CI attempt showed that directly bind-mounting empty cache directories onto `/nix` and `/root/.cargo` hid the builder image's existing tooling (`nix-build`, `/root/.cargo/env`, etc.). The workflow now uses a safer prewarm approach instead: it prebuilds the kernel in a temporary container, commits that container to a local one-off image, and replays the documented interactive developer flow on top of that prewarmed image + - the first prewarmed-image replay then exposed one more subtle issue: `docker commit` preserved the prewarm container's running command, so the derived image no longer dropped into an interactive shell by default. The `pexpect` driver now explicitly appends `bash` when starting both the end-user and kernel-developer outer containers + - the latest developer replay reached the local-kernel launch stage and then timed out waiting for the guest agent after switching to `/root/asterinas/target/osdk/aster-kernel-osdk-bin.qemu_elf`. That CI path had been building the local kernel from upstream `asterinas/asterinas` `main`, which can drift from the published `asterinas/asterinas:` image and packaged Kata release. The next adjustment is to make the CI checkout use the matching upstream release tag `v${ASTERINAS_VERSION}` so the local source tree aligns with the image/runtime bundle it is being validated against + - user review corrected that assumption: the docs workflow should not use upstream `asterinas/asterinas` at all for the developer source tree. To stay aligned with the existing release workflow, it should use `jjf-dev/asterinas` on the `kata-support` branch + - user review also requested richer release notes: the Asterinas repo/ref line should include the resolved Asterinas commit id, and the release notes should explicitly name the kata-containers commit id as well + - the same release-asset pre-resolution fix also needs to be applied to `.github/workflows/test-asterinas-kata.yml`, because its published-image matrix still calls `tools/kata/kata_env.sh install` with only `KATA_STATIC_TARBALL_RELEASE_REPO`, which reintroduces GitHub API rate-limit failures inside the job container + - the latest kernel-developer replay with `jjf-dev/asterinas@kata-support` got through both the packaged-kernel run and the local-kernel run; the remaining failure was only the final cleanup command using a relative `./tools/kata/kata_services.sh stop` after the script had changed directory into `/root/asterinas`. That cleanup path is now switched to the absolute repo path under `/root/kata-containers/tools/kata/kata_services.sh` + - per latest review, the CI-only prewarm/prebuild shortcut has been removed from `.github/workflows/test-asterinas-kata-docs.yml` so the kernel-developer path now goes back to launching the documented `asterinas/asterinas` image directly during replay +- Next steps: + - run a final quick local end-user replay after the latest script cleanup + - static-check the new workflow and script changes + - push and wait for CI to verify both documented flows end to end diff --git a/docs/doc-in-asterinas-repo.md b/docs/doc-in-asterinas-repo.md new file mode 100644 index 0000000000..dc799ac9bc --- /dev/null +++ b/docs/doc-in-asterinas-repo.md @@ -0,0 +1,134 @@ +Kata Containers is a VM-based container runtime. Each container runs inside its own virtual machine, so containers do not share the host kernel. + +Asterinas now supports Kata Containers and can serve as the guest kernel in common Kata-based scenarios. + +## For End Users +For end users, we provide the `asterinas/kata` container image, which already includes the dependencies and scripts required by Kata. + +You can try it with the following command: + +```bash +ASTERINAS_IMAGE_TAG=0.17.2-20260407 +docker run --rm -it \ + --cgroupns host \ + --privileged \ + --device /dev/kvm \ + --device /dev/vhost-net \ + --device /dev/vhost-vsock \ + --tmpfs /tmp:exec,mode=1777,size=8g \ + --tmpfs /var/lib/containerd:exec,mode=755,size=8g \ + asterinas/kata:${ASTERINAS_IMAGE_TAG} +``` + +After entering the container, start the background services required by Kata. At the moment, these services are `containerd` and `syslogd`: + +```bash +tools/kata/kata_services.sh start +``` + +You can check their status with the following command to make sure they started successfully: + +```bash +tools/kata/kata_services.sh status +``` + +Then you can use `nerdctl` with Kata to start an Alpine container: + +```bash +nerdctl run \ + --cgroup-manager cgroupfs \ + --net none \ + --runtime io.containerd.kata.v2 \ + --name foo \ + -it \ + docker.io/alpine:latest +``` + +If Docker Hub is temporarily unreachable during local validation, you can use +`docker.1ms.run/alpine:latest` instead. CI should keep using +`docker.io/alpine:latest`. + +Once the container starts, you will be inside Alpine. You can run the following commands to verify that you are no longer in the host environment: + +```bash +cat /proc/version +cat /etc/alpine-release +``` + +After you exit the container, remove it with: + +```bash +nerdctl rm foo +``` + +## For Kernel Developers +Kernel developers typically use the `asterinas/asterinas` container image. When starting the container, you also need to pass the additional arguments required by Kata: + +```bash +ASTERINAS_IMAGE_TAG=0.17.2-20260407 +ASTERINAS_SRC=$HOME/asterinas +KATA_SRC=$(git rev-parse --show-toplevel) +docker run --rm -it \ + --cgroupns host \ + --privileged \ + --device /dev/kvm \ + --device /dev/vhost-net \ + --device /dev/vhost-vsock \ + --tmpfs /tmp:exec,mode=1777,size=8g \ + --tmpfs /var/lib/containerd:exec,mode=755,size=8g \ + -v "${ASTERINAS_SRC}:/root/asterinas" \ + -v "${KATA_SRC}:/root/kata-containers" \ + -w /root/kata-containers \ + asterinas/asterinas:${ASTERINAS_IMAGE_TAG} +``` + +After entering the container, you will already be in `/root/kata-containers`. +The examples below assume the current kata-containers checkout is mounted there, +and that the local Asterinas tree is mounted at `/root/asterinas`. + +```bash +pwd +``` + +This script installs the files, scripts, and configuration needed to run Kata. After that, you can test Kata in the same way as an end user: + +```bash +# Install all dependencies +tools/kata/kata_env.sh install + +# Start background services +tools/kata/kata_services.sh start + +nerdctl run \ + --cgroup-manager cgroupfs \ + --net none \ + --runtime io.containerd.kata.v2 \ + --name foo \ + -it \ + docker.io/alpine:latest +``` + +If Docker Hub is temporarily unreachable during local validation, you can use +`docker.1ms.run/alpine:latest` here as well. CI should keep using +`docker.io/alpine:latest`. + +If `/dev/vsock` also exists on your host, it is fine to pass it through too, +but the documented minimum setup only requires `/dev/kvm`, `/dev/vhost-net`, +and `/dev/vhost-vsock`. + +You can also point Kata to a locally built guest kernel. Edit +`/etc/kata-containers/configuration.toml` and set `kernel` to the path of your +local kernel image: + +```toml +[hypervisor.qemu] +kernel = "/root/asterinas/target/osdk/aster-kernel-osdk-bin.qemu_elf" +``` + +Build the kernel with: + +```bash +cd /root/asterinas && make kernel BOOT_METHOD=qemu-direct +``` + +Then start the container with `nerdctl` again, and Kata will boot using your local kernel. diff --git a/src/runtime/go.mod b/src/runtime/go.mod index 5a5a011bb2..fc7e487be9 100644 --- a/src/runtime/go.mod +++ b/src/runtime/go.mod @@ -59,7 +59,7 @@ require ( golang.org/x/oauth2 v0.30.0 golang.org/x/sys v0.40.0 google.golang.org/grpc v1.72.0 - google.golang.org/protobuf v1.36.7 + google.golang.org/protobuf v1.36.11 k8s.io/apimachinery v0.33.0 k8s.io/cri-api v0.33.0 k8s.io/kubelet v0.33.0 diff --git a/src/runtime/go.sum b/src/runtime/go.sum index 5ed53e0bcb..4424cb768d 100644 --- a/src/runtime/go.sum +++ b/src/runtime/go.sum @@ -444,8 +444,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/src/runtime/vendor/google.golang.org/protobuf/internal/encoding/tag/tag.go b/src/runtime/vendor/google.golang.org/protobuf/internal/encoding/tag/tag.go index 669133d04d..c96e448346 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/internal/encoding/tag/tag.go +++ b/src/runtime/vendor/google.golang.org/protobuf/internal/encoding/tag/tag.go @@ -32,7 +32,7 @@ var byteType = reflect.TypeOf(byte(0)) func Unmarshal(tag string, goType reflect.Type, evs protoreflect.EnumValueDescriptors) protoreflect.FieldDescriptor { f := new(filedesc.Field) f.L0.ParentFile = filedesc.SurrogateProto2 - f.L1.EditionFeatures = f.L0.ParentFile.L1.EditionFeatures + packed := false for len(tag) > 0 { i := strings.IndexByte(tag, ',') if i < 0 { @@ -108,7 +108,7 @@ func Unmarshal(tag string, goType reflect.Type, evs protoreflect.EnumValueDescri f.L1.StringName.InitJSON(jsonName) } case s == "packed": - f.L1.EditionFeatures.IsPacked = true + packed = true case strings.HasPrefix(s, "def="): // The default tag is special in that everything afterwards is the // default regardless of the presence of commas. @@ -121,6 +121,13 @@ func Unmarshal(tag string, goType reflect.Type, evs protoreflect.EnumValueDescri tag = strings.TrimPrefix(tag[i:], ",") } + // Update EditionFeatures after the loop and after we know whether this is + // a proto2 or proto3 field. + f.L1.EditionFeatures = f.L0.ParentFile.L1.EditionFeatures + if packed { + f.L1.EditionFeatures.IsPacked = true + } + // The generator uses the group message name instead of the field name. // We obtain the real field name by lowercasing the group name. if f.L1.Kind == protoreflect.GroupKind { diff --git a/src/runtime/vendor/google.golang.org/protobuf/internal/encoding/text/decode.go b/src/runtime/vendor/google.golang.org/protobuf/internal/encoding/text/decode.go index 099b2bf451..9aa7a9bb77 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/internal/encoding/text/decode.go +++ b/src/runtime/vendor/google.golang.org/protobuf/internal/encoding/text/decode.go @@ -424,27 +424,34 @@ func (d *Decoder) parseFieldName() (tok Token, err error) { return Token{}, d.newSyntaxError("invalid field name: %s", errId(d.in)) } -// parseTypeName parses Any type URL or extension field name. The name is -// enclosed in [ and ] characters. The C++ parser does not handle many legal URL -// strings. This implementation is more liberal and allows for the pattern -// ^[-_a-zA-Z0-9]+([./][-_a-zA-Z0-9]+)*`). Whitespaces and comments are allowed -// in between [ ], '.', '/' and the sub names. +// parseTypeName parses an Any type URL or an extension field name. The name is +// enclosed in [ and ] characters. We allow almost arbitrary type URL prefixes, +// closely following the text-format spec [1,2]. We implement "ExtensionName | +// AnyName" as follows (with some exceptions for backwards compatibility): +// +// char = [-_a-zA-Z0-9] +// url_char = char | [.~!$&'()*+,;=] | "%", hex, hex +// +// Ident = char, { char } +// TypeName = Ident, { ".", Ident } ; +// UrlPrefix = url_char, { url_char | "/" } ; +// ExtensionName = "[", TypeName, "]" ; +// AnyName = "[", UrlPrefix, "/", TypeName, "]" ; +// +// Additionally, we allow arbitrary whitespace and comments between [ and ]. +// +// [1] https://protobuf.dev/reference/protobuf/textformat-spec/#characters +// [2] https://protobuf.dev/reference/protobuf/textformat-spec/#field-names func (d *Decoder) parseTypeName() (Token, error) { - startPos := len(d.orig) - len(d.in) // Use alias s to advance first in order to use d.in for error handling. - // Caller already checks for [ as first character. + // Caller already checks for [ as first character (d.in[0] == '['). s := consume(d.in[1:], 0) if len(s) == 0 { return Token{}, ErrUnexpectedEOF } + // Collect everything between [ and ] in name. var name []byte - for len(s) > 0 && isTypeNameChar(s[0]) { - name = append(name, s[0]) - s = s[1:] - } - s = consume(s, 0) - var closed bool for len(s) > 0 && !closed { switch { @@ -452,23 +459,20 @@ func (d *Decoder) parseTypeName() (Token, error) { s = s[1:] closed = true - case s[0] == '/', s[0] == '.': - if len(name) > 0 && (name[len(name)-1] == '/' || name[len(name)-1] == '.') { - return Token{}, d.newSyntaxError("invalid type URL/extension field name: %s", - d.orig[startPos:len(d.orig)-len(s)+1]) - } + case s[0] == '/' || isTypeNameChar(s[0]) || isUrlExtraChar(s[0]): name = append(name, s[0]) - s = s[1:] - s = consume(s, 0) - for len(s) > 0 && isTypeNameChar(s[0]) { - name = append(name, s[0]) - s = s[1:] + s = consume(s[1:], 0) + + // URL percent-encoded chars + case s[0] == '%': + if len(s) < 3 || !isHexChar(s[1]) || !isHexChar(s[2]) { + return Token{}, d.parseTypeNameError(s, 3) } - s = consume(s, 0) + name = append(name, s[0], s[1], s[2]) + s = consume(s[3:], 0) default: - return Token{}, d.newSyntaxError( - "invalid type URL/extension field name: %s", d.orig[startPos:len(d.orig)-len(s)+1]) + return Token{}, d.parseTypeNameError(s, 1) } } @@ -476,15 +480,38 @@ func (d *Decoder) parseTypeName() (Token, error) { return Token{}, ErrUnexpectedEOF } - // First character cannot be '.'. Last character cannot be '.' or '/'. - size := len(name) - if size == 0 || name[0] == '.' || name[size-1] == '.' || name[size-1] == '/' { - return Token{}, d.newSyntaxError("invalid type URL/extension field name: %s", - d.orig[startPos:len(d.orig)-len(s)]) + // Split collected name on last '/' into urlPrefix and typeName (if '/' is + // present). + typeName := name + if i := bytes.LastIndexByte(name, '/'); i != -1 { + urlPrefix := name[:i] + typeName = name[i+1:] + + // urlPrefix may be empty (for backwards compatibility). + // If non-empty, it must not start with '/'. + if len(urlPrefix) > 0 && urlPrefix[0] == '/' { + return Token{}, d.parseTypeNameError(s, 0) + } } + // typeName must not be empty (note: "" splits to [""]) and all identifier + // parts must not be empty. + for _, ident := range bytes.Split(typeName, []byte{'.'}) { + if len(ident) == 0 { + return Token{}, d.parseTypeNameError(s, 0) + } + } + + // typeName must not contain any percent-encoded or special URL chars. + for _, b := range typeName { + if b == '%' || (b != '.' && isUrlExtraChar(b)) { + return Token{}, d.parseTypeNameError(s, 0) + } + } + + startPos := len(d.orig) - len(d.in) + endPos := len(d.orig) - len(s) d.in = s - endPos := len(d.orig) - len(d.in) d.consume(0) return Token{ @@ -496,16 +523,32 @@ func (d *Decoder) parseTypeName() (Token, error) { }, nil } +func (d *Decoder) parseTypeNameError(s []byte, numUnconsumedChars int) error { + return d.newSyntaxError( + "invalid type URL/extension field name: %s", + d.in[:len(d.in)-len(s)+min(numUnconsumedChars, len(s))], + ) +} + +func isHexChar(b byte) bool { + return ('0' <= b && b <= '9') || + ('a' <= b && b <= 'f') || + ('A' <= b && b <= 'F') +} + func isTypeNameChar(b byte) bool { - return (b == '-' || b == '_' || + return b == '-' || b == '_' || ('0' <= b && b <= '9') || ('a' <= b && b <= 'z') || - ('A' <= b && b <= 'Z')) + ('A' <= b && b <= 'Z') } -func isWhiteSpace(b byte) bool { +// isUrlExtraChar complements isTypeNameChar with extra characters that we allow +// in URLs but not in type names. Note that '/' is not included so that it can +// be treated specially. +func isUrlExtraChar(b byte) bool { switch b { - case ' ', '\n', '\r', '\t': + case '.', '~', '!', '$', '&', '(', ')', '*', '+', ',', ';', '=': return true default: return false diff --git a/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/desc.go b/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/desc.go index 688aabe434..c775e5832f 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/desc.go +++ b/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/desc.go @@ -32,6 +32,7 @@ const ( EditionProto3 Edition = 999 Edition2023 Edition = 1000 Edition2024 Edition = 1001 + EditionUnstable Edition = 9999 EditionUnsupported Edition = 100000 ) @@ -72,9 +73,10 @@ type ( EditionFeatures EditionFeatures } FileL2 struct { - Options func() protoreflect.ProtoMessage - Imports FileImports - Locations SourceLocations + Options func() protoreflect.ProtoMessage + Imports FileImports + OptionImports func() protoreflect.FileImports + Locations SourceLocations } // EditionFeatures is a frequently-instantiated struct, so please take care @@ -126,12 +128,9 @@ func (fd *File) ParentFile() protoreflect.FileDescriptor { return fd } func (fd *File) Parent() protoreflect.Descriptor { return nil } func (fd *File) Index() int { return 0 } func (fd *File) Syntax() protoreflect.Syntax { return fd.L1.Syntax } - -// Not exported and just used to reconstruct the original FileDescriptor proto -func (fd *File) Edition() int32 { return int32(fd.L1.Edition) } -func (fd *File) Name() protoreflect.Name { return fd.L1.Package.Name() } -func (fd *File) FullName() protoreflect.FullName { return fd.L1.Package } -func (fd *File) IsPlaceholder() bool { return false } +func (fd *File) Name() protoreflect.Name { return fd.L1.Package.Name() } +func (fd *File) FullName() protoreflect.FullName { return fd.L1.Package } +func (fd *File) IsPlaceholder() bool { return false } func (fd *File) Options() protoreflect.ProtoMessage { if f := fd.lazyInit().Options; f != nil { return f() @@ -150,6 +149,16 @@ func (fd *File) Format(s fmt.State, r rune) { descfmt.FormatD func (fd *File) ProtoType(protoreflect.FileDescriptor) {} func (fd *File) ProtoInternal(pragma.DoNotImplement) {} +// The next two are not part of the FileDescriptor interface. They are just used to reconstruct +// the original FileDescriptor proto. +func (fd *File) Edition() int32 { return int32(fd.L1.Edition) } +func (fd *File) OptionImports() protoreflect.FileImports { + if f := fd.lazyInit().OptionImports; f != nil { + return f() + } + return emptyFiles +} + func (fd *File) lazyInit() *FileL2 { if atomic.LoadUint32(&fd.once) == 0 { fd.lazyInitOnce() @@ -182,9 +191,9 @@ type ( L2 *EnumL2 // protected by fileDesc.once } EnumL1 struct { - eagerValues bool // controls whether EnumL2.Values is already populated - EditionFeatures EditionFeatures + Visibility int32 + eagerValues bool // controls whether EnumL2.Values is already populated } EnumL2 struct { Options func() protoreflect.ProtoMessage @@ -219,6 +228,11 @@ func (ed *Enum) ReservedNames() protoreflect.Names { return &ed.lazyInit() func (ed *Enum) ReservedRanges() protoreflect.EnumRanges { return &ed.lazyInit().ReservedRanges } func (ed *Enum) Format(s fmt.State, r rune) { descfmt.FormatDesc(s, r, ed) } func (ed *Enum) ProtoType(protoreflect.EnumDescriptor) {} + +// This is not part of the EnumDescriptor interface. It is just used to reconstruct +// the original FileDescriptor proto. +func (ed *Enum) Visibility() int32 { return ed.L1.Visibility } + func (ed *Enum) lazyInit() *EnumL2 { ed.L0.ParentFile.lazyInit() // implicitly initializes L2 return ed.L2 @@ -244,13 +258,13 @@ type ( L2 *MessageL2 // protected by fileDesc.once } MessageL1 struct { - Enums Enums - Messages Messages - Extensions Extensions - IsMapEntry bool // promoted from google.protobuf.MessageOptions - IsMessageSet bool // promoted from google.protobuf.MessageOptions - + Enums Enums + Messages Messages + Extensions Extensions EditionFeatures EditionFeatures + Visibility int32 + IsMapEntry bool // promoted from google.protobuf.MessageOptions + IsMessageSet bool // promoted from google.protobuf.MessageOptions } MessageL2 struct { Options func() protoreflect.ProtoMessage @@ -319,6 +333,11 @@ func (md *Message) Messages() protoreflect.MessageDescriptors { return &md.L func (md *Message) Extensions() protoreflect.ExtensionDescriptors { return &md.L1.Extensions } func (md *Message) ProtoType(protoreflect.MessageDescriptor) {} func (md *Message) Format(s fmt.State, r rune) { descfmt.FormatDesc(s, r, md) } + +// This is not part of the MessageDescriptor interface. It is just used to reconstruct +// the original FileDescriptor proto. +func (md *Message) Visibility() int32 { return md.L1.Visibility } + func (md *Message) lazyInit() *MessageL2 { md.L0.ParentFile.lazyInit() // implicitly initializes L2 return md.L2 diff --git a/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/desc_init.go b/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/desc_init.go index d2f549497e..e91860f5a2 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/desc_init.go +++ b/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/desc_init.go @@ -284,6 +284,13 @@ func (ed *Enum) unmarshalSeed(b []byte, sb *strs.Builder, pf *File, pd protorefl case genid.EnumDescriptorProto_Value_field_number: numValues++ } + case protowire.VarintType: + v, m := protowire.ConsumeVarint(b) + b = b[m:] + switch num { + case genid.EnumDescriptorProto_Visibility_field_number: + ed.L1.Visibility = int32(v) + } default: m := protowire.ConsumeFieldValue(num, typ, b) b = b[m:] @@ -365,6 +372,13 @@ func (md *Message) unmarshalSeed(b []byte, sb *strs.Builder, pf *File, pd protor md.unmarshalSeedOptions(v) } prevField = num + case protowire.VarintType: + v, m := protowire.ConsumeVarint(b) + b = b[m:] + switch num { + case genid.DescriptorProto_Visibility_field_number: + md.L1.Visibility = int32(v) + } default: m := protowire.ConsumeFieldValue(num, typ, b) b = b[m:] diff --git a/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go b/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go index d4c94458bd..78f02b1b49 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go +++ b/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go @@ -134,6 +134,7 @@ func (fd *File) unmarshalFull(b []byte) { var enumIdx, messageIdx, extensionIdx, serviceIdx int var rawOptions []byte + var optionImports []string fd.L2 = new(FileL2) for len(b) > 0 { num, typ, n := protowire.ConsumeTag(b) @@ -157,6 +158,8 @@ func (fd *File) unmarshalFull(b []byte) { imp = PlaceholderFile(path) } fd.L2.Imports = append(fd.L2.Imports, protoreflect.FileImport{FileDescriptor: imp}) + case genid.FileDescriptorProto_OptionDependency_field_number: + optionImports = append(optionImports, sb.MakeString(v)) case genid.FileDescriptorProto_EnumType_field_number: fd.L1.Enums.List[enumIdx].unmarshalFull(v, sb) enumIdx++ @@ -178,6 +181,23 @@ func (fd *File) unmarshalFull(b []byte) { } } fd.L2.Options = fd.builder.optionsUnmarshaler(&descopts.File, rawOptions) + if len(optionImports) > 0 { + var imps FileImports + var once sync.Once + fd.L2.OptionImports = func() protoreflect.FileImports { + once.Do(func() { + imps = make(FileImports, len(optionImports)) + for i, path := range optionImports { + imp, _ := fd.builder.FileRegistry.FindFileByPath(path) + if imp == nil { + imp = PlaceholderFile(path) + } + imps[i] = protoreflect.FileImport{FileDescriptor: imp} + } + }) + return &imps + } + } } func (ed *Enum) unmarshalFull(b []byte, sb *strs.Builder) { @@ -310,7 +330,6 @@ func (md *Message) unmarshalFull(b []byte, sb *strs.Builder) { md.L1.Extensions.List[extensionIdx].unmarshalFull(v, sb) extensionIdx++ case genid.DescriptorProto_Options_field_number: - md.unmarshalOptions(v) rawOptions = appendOptions(rawOptions, v) } default: @@ -336,27 +355,6 @@ func (md *Message) unmarshalFull(b []byte, sb *strs.Builder) { md.L2.Options = md.L0.ParentFile.builder.optionsUnmarshaler(&descopts.Message, rawOptions) } -func (md *Message) unmarshalOptions(b []byte) { - for len(b) > 0 { - num, typ, n := protowire.ConsumeTag(b) - b = b[n:] - switch typ { - case protowire.VarintType: - v, m := protowire.ConsumeVarint(b) - b = b[m:] - switch num { - case genid.MessageOptions_MapEntry_field_number: - md.L1.IsMapEntry = protowire.DecodeBool(v) - case genid.MessageOptions_MessageSetWireFormat_field_number: - md.L1.IsMessageSet = protowire.DecodeBool(v) - } - default: - m := protowire.ConsumeFieldValue(num, typ, b) - b = b[m:] - } - } -} - func unmarshalMessageReservedRange(b []byte) (r [2]protoreflect.FieldNumber) { for len(b) > 0 { num, typ, n := protowire.ConsumeTag(b) diff --git a/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/editions.go b/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/editions.go index a0aad2777f..66ba906806 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/editions.go +++ b/src/runtime/vendor/google.golang.org/protobuf/internal/filedesc/editions.go @@ -13,8 +13,10 @@ import ( "google.golang.org/protobuf/reflect/protoreflect" ) -var defaultsCache = make(map[Edition]EditionFeatures) -var defaultsKeys = []Edition{} +var ( + defaultsCache = make(map[Edition]EditionFeatures) + defaultsKeys = []Edition{} +) func init() { unmarshalEditionDefaults(editiondefaults.Defaults) @@ -41,7 +43,7 @@ func unmarshalGoFeature(b []byte, parent EditionFeatures) EditionFeatures { b = b[m:] parent.StripEnumPrefix = int(v) default: - panic(fmt.Sprintf("unkown field number %d while unmarshalling GoFeatures", num)) + panic(fmt.Sprintf("unknown field number %d while unmarshalling GoFeatures", num)) } } return parent @@ -76,7 +78,7 @@ func unmarshalFeatureSet(b []byte, parent EditionFeatures) EditionFeatures { // DefaultSymbolVisibility is enforced in protoc, runtimes should not // inspect this value. default: - panic(fmt.Sprintf("unkown field number %d while unmarshalling FeatureSet", num)) + panic(fmt.Sprintf("unknown field number %d while unmarshalling FeatureSet", num)) } case protowire.BytesType: v, m := protowire.ConsumeBytes(b) @@ -150,7 +152,7 @@ func unmarshalEditionDefaults(b []byte) { _, m := protowire.ConsumeVarint(b) b = b[m:] default: - panic(fmt.Sprintf("unkown field number %d while unmarshalling EditionDefault", num)) + panic(fmt.Sprintf("unknown field number %d while unmarshalling EditionDefault", num)) } } } diff --git a/src/runtime/vendor/google.golang.org/protobuf/internal/genid/api_gen.go b/src/runtime/vendor/google.golang.org/protobuf/internal/genid/api_gen.go index df8f918501..3ceb6fa7f5 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/internal/genid/api_gen.go +++ b/src/runtime/vendor/google.golang.org/protobuf/internal/genid/api_gen.go @@ -27,6 +27,7 @@ const ( Api_SourceContext_field_name protoreflect.Name = "source_context" Api_Mixins_field_name protoreflect.Name = "mixins" Api_Syntax_field_name protoreflect.Name = "syntax" + Api_Edition_field_name protoreflect.Name = "edition" Api_Name_field_fullname protoreflect.FullName = "google.protobuf.Api.name" Api_Methods_field_fullname protoreflect.FullName = "google.protobuf.Api.methods" @@ -35,6 +36,7 @@ const ( Api_SourceContext_field_fullname protoreflect.FullName = "google.protobuf.Api.source_context" Api_Mixins_field_fullname protoreflect.FullName = "google.protobuf.Api.mixins" Api_Syntax_field_fullname protoreflect.FullName = "google.protobuf.Api.syntax" + Api_Edition_field_fullname protoreflect.FullName = "google.protobuf.Api.edition" ) // Field numbers for google.protobuf.Api. @@ -46,6 +48,7 @@ const ( Api_SourceContext_field_number protoreflect.FieldNumber = 5 Api_Mixins_field_number protoreflect.FieldNumber = 6 Api_Syntax_field_number protoreflect.FieldNumber = 7 + Api_Edition_field_number protoreflect.FieldNumber = 8 ) // Names for google.protobuf.Method. @@ -63,6 +66,7 @@ const ( Method_ResponseStreaming_field_name protoreflect.Name = "response_streaming" Method_Options_field_name protoreflect.Name = "options" Method_Syntax_field_name protoreflect.Name = "syntax" + Method_Edition_field_name protoreflect.Name = "edition" Method_Name_field_fullname protoreflect.FullName = "google.protobuf.Method.name" Method_RequestTypeUrl_field_fullname protoreflect.FullName = "google.protobuf.Method.request_type_url" @@ -71,6 +75,7 @@ const ( Method_ResponseStreaming_field_fullname protoreflect.FullName = "google.protobuf.Method.response_streaming" Method_Options_field_fullname protoreflect.FullName = "google.protobuf.Method.options" Method_Syntax_field_fullname protoreflect.FullName = "google.protobuf.Method.syntax" + Method_Edition_field_fullname protoreflect.FullName = "google.protobuf.Method.edition" ) // Field numbers for google.protobuf.Method. @@ -82,6 +87,7 @@ const ( Method_ResponseStreaming_field_number protoreflect.FieldNumber = 5 Method_Options_field_number protoreflect.FieldNumber = 6 Method_Syntax_field_number protoreflect.FieldNumber = 7 + Method_Edition_field_number protoreflect.FieldNumber = 8 ) // Names for google.protobuf.Mixin. diff --git a/src/runtime/vendor/google.golang.org/protobuf/internal/genid/descriptor_gen.go b/src/runtime/vendor/google.golang.org/protobuf/internal/genid/descriptor_gen.go index 950a6a325a..65aaf4d210 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/internal/genid/descriptor_gen.go +++ b/src/runtime/vendor/google.golang.org/protobuf/internal/genid/descriptor_gen.go @@ -26,6 +26,7 @@ const ( Edition_EDITION_PROTO3_enum_value = 999 Edition_EDITION_2023_enum_value = 1000 Edition_EDITION_2024_enum_value = 1001 + Edition_EDITION_UNSTABLE_enum_value = 9999 Edition_EDITION_1_TEST_ONLY_enum_value = 1 Edition_EDITION_2_TEST_ONLY_enum_value = 2 Edition_EDITION_99997_TEST_ONLY_enum_value = 99997 diff --git a/src/runtime/vendor/google.golang.org/protobuf/internal/impl/codec_map.go b/src/runtime/vendor/google.golang.org/protobuf/internal/impl/codec_map.go index 229c698013..4a3bf393ef 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/internal/impl/codec_map.go +++ b/src/runtime/vendor/google.golang.org/protobuf/internal/impl/codec_map.go @@ -113,6 +113,9 @@ func sizeMap(mapv reflect.Value, mapi *mapInfo, f *coderFieldInfo, opts marshalO } func consumeMap(b []byte, mapv reflect.Value, wtyp protowire.Type, mapi *mapInfo, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) { + if opts.depth--; opts.depth < 0 { + return out, errRecursionDepth + } if wtyp != protowire.BytesType { return out, errUnknown } @@ -170,6 +173,9 @@ func consumeMap(b []byte, mapv reflect.Value, wtyp protowire.Type, mapi *mapInfo } func consumeMapOfMessage(b []byte, mapv reflect.Value, wtyp protowire.Type, mapi *mapInfo, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) { + if opts.depth--; opts.depth < 0 { + return out, errRecursionDepth + } if wtyp != protowire.BytesType { return out, errUnknown } diff --git a/src/runtime/vendor/google.golang.org/protobuf/internal/impl/decode.go b/src/runtime/vendor/google.golang.org/protobuf/internal/impl/decode.go index e0dd21fa5f..1228b5c8c2 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/internal/impl/decode.go +++ b/src/runtime/vendor/google.golang.org/protobuf/internal/impl/decode.go @@ -102,8 +102,7 @@ var errUnknown = errors.New("unknown") func (mi *MessageInfo) unmarshalPointer(b []byte, p pointer, groupTag protowire.Number, opts unmarshalOptions) (out unmarshalOutput, err error) { mi.init() - opts.depth-- - if opts.depth < 0 { + if opts.depth--; opts.depth < 0 { return out, errRecursionDepth } if flags.ProtoLegacy && mi.isMessageSet { diff --git a/src/runtime/vendor/google.golang.org/protobuf/internal/impl/validate.go b/src/runtime/vendor/google.golang.org/protobuf/internal/impl/validate.go index 7b2995dde5..99a1eb95f7 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/internal/impl/validate.go +++ b/src/runtime/vendor/google.golang.org/protobuf/internal/impl/validate.go @@ -68,9 +68,13 @@ func Validate(mt protoreflect.MessageType, in protoiface.UnmarshalInput) (out pr if in.Resolver == nil { in.Resolver = protoregistry.GlobalTypes } + if in.Depth == 0 { + in.Depth = protowire.DefaultRecursionLimit + } o, st := mi.validate(in.Buf, 0, unmarshalOptions{ flags: in.Flags, resolver: in.Resolver, + depth: in.Depth, }) if o.initialized { out.Flags |= protoiface.UnmarshalInitialized @@ -257,6 +261,9 @@ func (mi *MessageInfo) validate(b []byte, groupTag protowire.Number, opts unmars states[0].typ = validationTypeGroup states[0].endGroup = groupTag } + if opts.depth--; opts.depth < 0 { + return out, ValidationInvalid + } initialized := true start := len(b) State: @@ -451,6 +458,13 @@ State: mi: vi.mi, tail: b, }) + if vi.typ == validationTypeMessage || + vi.typ == validationTypeGroup || + vi.typ == validationTypeMap { + if opts.depth--; opts.depth < 0 { + return out, ValidationInvalid + } + } b = v continue State case validationTypeRepeatedVarint: @@ -499,6 +513,9 @@ State: mi: vi.mi, endGroup: num, }) + if opts.depth--; opts.depth < 0 { + return out, ValidationInvalid + } continue State case flags.ProtoLegacy && vi.typ == validationTypeMessageSetItem: typeid, v, n, err := messageset.ConsumeFieldValue(b, false) @@ -521,6 +538,13 @@ State: mi: xvi.mi, tail: b[n:], }) + if xvi.typ == validationTypeMessage || + xvi.typ == validationTypeGroup || + xvi.typ == validationTypeMap { + if opts.depth--; opts.depth < 0 { + return out, ValidationInvalid + } + } b = v continue State } @@ -547,12 +571,14 @@ State: switch st.typ { case validationTypeMessage, validationTypeGroup: numRequiredFields = int(st.mi.numRequiredFields) + opts.depth++ case validationTypeMap: // If this is a map field with a message value that contains // required fields, require that the value be present. if st.mi != nil && st.mi.numRequiredFields > 0 { numRequiredFields = 1 } + opts.depth++ } // If there are more than 64 required fields, this check will // always fail and we will report that the message is potentially diff --git a/src/runtime/vendor/google.golang.org/protobuf/internal/version/version.go b/src/runtime/vendor/google.golang.org/protobuf/internal/version/version.go index a53364c599..763fd82841 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/internal/version/version.go +++ b/src/runtime/vendor/google.golang.org/protobuf/internal/version/version.go @@ -52,7 +52,7 @@ import ( const ( Major = 1 Minor = 36 - Patch = 7 + Patch = 11 PreRelease = "" ) diff --git a/src/runtime/vendor/google.golang.org/protobuf/proto/decode.go b/src/runtime/vendor/google.golang.org/protobuf/proto/decode.go index 4cbf1aeaf7..889d8511d2 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/proto/decode.go +++ b/src/runtime/vendor/google.golang.org/protobuf/proto/decode.go @@ -121,9 +121,8 @@ func (o UnmarshalOptions) unmarshal(b []byte, m protoreflect.Message) (out proto out, err = methods.Unmarshal(in) } else { - o.RecursionLimit-- - if o.RecursionLimit < 0 { - return out, errors.New("exceeded max recursion depth") + if o.RecursionLimit--; o.RecursionLimit < 0 { + return out, errRecursionDepth } err = o.unmarshalMessageSlow(b, m) } @@ -220,6 +219,9 @@ func (o UnmarshalOptions) unmarshalSingular(b []byte, wtyp protowire.Type, m pro } func (o UnmarshalOptions) unmarshalMap(b []byte, wtyp protowire.Type, mapv protoreflect.Map, fd protoreflect.FieldDescriptor) (n int, err error) { + if o.RecursionLimit--; o.RecursionLimit < 0 { + return 0, errRecursionDepth + } if wtyp != protowire.BytesType { return 0, errUnknown } @@ -305,3 +307,5 @@ func (o UnmarshalOptions) unmarshalMap(b []byte, wtyp protowire.Type, mapv proto var errUnknown = errors.New("BUG: internal error (unknown)") var errDecode = errors.New("cannot parse invalid wire-format data") + +var errRecursionDepth = errors.New("exceeded maximum recursion depth") diff --git a/src/runtime/vendor/google.golang.org/protobuf/types/descriptorpb/descriptor.pb.go b/src/runtime/vendor/google.golang.org/protobuf/types/descriptorpb/descriptor.pb.go index 6843b0beaf..0b23faa957 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/types/descriptorpb/descriptor.pb.go +++ b/src/runtime/vendor/google.golang.org/protobuf/types/descriptorpb/descriptor.pb.go @@ -69,6 +69,8 @@ const ( // comparison. Edition_EDITION_2023 Edition = 1000 Edition_EDITION_2024 Edition = 1001 + // A placeholder edition for developing and testing unscheduled features. + Edition_EDITION_UNSTABLE Edition = 9999 // Placeholder editions for testing feature resolution. These should not be // used or relied on outside of tests. Edition_EDITION_1_TEST_ONLY Edition = 1 @@ -91,6 +93,7 @@ var ( 999: "EDITION_PROTO3", 1000: "EDITION_2023", 1001: "EDITION_2024", + 9999: "EDITION_UNSTABLE", 1: "EDITION_1_TEST_ONLY", 2: "EDITION_2_TEST_ONLY", 99997: "EDITION_99997_TEST_ONLY", @@ -105,6 +108,7 @@ var ( "EDITION_PROTO3": 999, "EDITION_2023": 1000, "EDITION_2024": 1001, + "EDITION_UNSTABLE": 9999, "EDITION_1_TEST_ONLY": 1, "EDITION_2_TEST_ONLY": 2, "EDITION_99997_TEST_ONLY": 99997, @@ -2873,7 +2877,10 @@ type FieldOptions struct { // for accessors, or it will be completely ignored; in the very least, this // is a formalization for deprecating fields. Deprecated *bool `protobuf:"varint,3,opt,name=deprecated,def=0" json:"deprecated,omitempty"` + // DEPRECATED. DO NOT USE! // For Google-internal migration only. Do not use. + // + // Deprecated: Marked as deprecated in google/protobuf/descriptor.proto. Weak *bool `protobuf:"varint,10,opt,name=weak,def=0" json:"weak,omitempty"` // Indicate that the field value should not be printed out when using debug // formats, e.g. when the field contains sensitive credentials. @@ -2977,6 +2984,7 @@ func (x *FieldOptions) GetDeprecated() bool { return Default_FieldOptions_Deprecated } +// Deprecated: Marked as deprecated in google/protobuf/descriptor.proto. func (x *FieldOptions) GetWeak() bool { if x != nil && x.Weak != nil { return *x.Weak @@ -4789,11 +4797,11 @@ const file_google_protobuf_descriptor_proto_rawDesc = "" + "\x18EnumValueDescriptorProto\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + "\x06number\x18\x02 \x01(\x05R\x06number\x12;\n" + - "\aoptions\x18\x03 \x01(\v2!.google.protobuf.EnumValueOptionsR\aoptions\"\xa7\x01\n" + + "\aoptions\x18\x03 \x01(\v2!.google.protobuf.EnumValueOptionsR\aoptions\"\xb5\x01\n" + "\x16ServiceDescriptorProto\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12>\n" + "\x06method\x18\x02 \x03(\v2&.google.protobuf.MethodDescriptorProtoR\x06method\x129\n" + - "\aoptions\x18\x03 \x01(\v2\x1f.google.protobuf.ServiceOptionsR\aoptions\"\x89\x02\n" + + "\aoptions\x18\x03 \x01(\v2\x1f.google.protobuf.ServiceOptionsR\aoptionsJ\x04\b\x04\x10\x05R\x06stream\"\x89\x02\n" + "\x15MethodDescriptorProto\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n" + "\n" + @@ -4843,7 +4851,7 @@ const file_google_protobuf_descriptor_proto_rawDesc = "" + "&deprecated_legacy_json_field_conflicts\x18\v \x01(\bB\x02\x18\x01R\"deprecatedLegacyJsonFieldConflicts\x127\n" + "\bfeatures\x18\f \x01(\v2\x1b.google.protobuf.FeatureSetR\bfeatures\x12X\n" + "\x14uninterpreted_option\x18\xe7\a \x03(\v2$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\b\xe8\a\x10\x80\x80\x80\x80\x02J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06J\x04\b\x06\x10\aJ\x04\b\b\x10\tJ\x04\b\t\x10\n" + - "\"\x9d\r\n" + + "\"\xa1\r\n" + "\fFieldOptions\x12A\n" + "\x05ctype\x18\x01 \x01(\x0e2#.google.protobuf.FieldOptions.CType:\x06STRINGR\x05ctype\x12\x16\n" + "\x06packed\x18\x02 \x01(\bR\x06packed\x12G\n" + @@ -4852,9 +4860,9 @@ const file_google_protobuf_descriptor_proto_rawDesc = "" + "\x0funverified_lazy\x18\x0f \x01(\b:\x05falseR\x0eunverifiedLazy\x12%\n" + "\n" + "deprecated\x18\x03 \x01(\b:\x05falseR\n" + - "deprecated\x12\x19\n" + + "deprecated\x12\x1d\n" + "\x04weak\x18\n" + - " \x01(\b:\x05falseR\x04weak\x12(\n" + + " \x01(\b:\x05falseB\x02\x18\x01R\x04weak\x12(\n" + "\fdebug_redact\x18\x10 \x01(\b:\x05falseR\vdebugRedact\x12K\n" + "\tretention\x18\x11 \x01(\x0e2-.google.protobuf.FieldOptions.OptionRetentionR\tretention\x12H\n" + "\atargets\x18\x13 \x03(\x0e2..google.protobuf.FieldOptions.OptionTargetTypeR\atargets\x12W\n" + @@ -5029,14 +5037,15 @@ const file_google_protobuf_descriptor_proto_rawDesc = "" + "\bSemantic\x12\b\n" + "\x04NONE\x10\x00\x12\a\n" + "\x03SET\x10\x01\x12\t\n" + - "\x05ALIAS\x10\x02*\xa7\x02\n" + + "\x05ALIAS\x10\x02*\xbe\x02\n" + "\aEdition\x12\x13\n" + "\x0fEDITION_UNKNOWN\x10\x00\x12\x13\n" + "\x0eEDITION_LEGACY\x10\x84\a\x12\x13\n" + "\x0eEDITION_PROTO2\x10\xe6\a\x12\x13\n" + "\x0eEDITION_PROTO3\x10\xe7\a\x12\x11\n" + "\fEDITION_2023\x10\xe8\a\x12\x11\n" + - "\fEDITION_2024\x10\xe9\a\x12\x17\n" + + "\fEDITION_2024\x10\xe9\a\x12\x15\n" + + "\x10EDITION_UNSTABLE\x10\x8fN\x12\x17\n" + "\x13EDITION_1_TEST_ONLY\x10\x01\x12\x17\n" + "\x13EDITION_2_TEST_ONLY\x10\x02\x12\x1d\n" + "\x17EDITION_99997_TEST_ONLY\x10\x9d\x8d\x06\x12\x1d\n" + diff --git a/src/runtime/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go b/src/runtime/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go index 06d584c14b..484c21fd53 100644 --- a/src/runtime/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go +++ b/src/runtime/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go @@ -172,13 +172,14 @@ import ( // ) to obtain a formatter capable of generating timestamps in this format. type Timestamp struct { state protoimpl.MessageState `protogen:"open.v1"` - // Represents seconds of UTC time since Unix epoch - // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to - // 9999-12-31T23:59:59Z inclusive. + // Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must + // be between -315576000000 and 315576000000 inclusive (which corresponds to + // 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z). Seconds int64 `protobuf:"varint,1,opt,name=seconds,proto3" json:"seconds,omitempty"` - // Non-negative fractions of a second at nanosecond resolution. Negative - // second values with fractions must still have non-negative nanos values - // that count forward in time. Must be from 0 to 999,999,999 + // Non-negative fractions of a second at nanosecond resolution. This field is + // the nanosecond portion of the duration, not an alternative to seconds. + // Negative second values with fractions must still have non-negative nanos + // values that count forward in time. Must be between 0 and 999,999,999 // inclusive. Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"` unknownFields protoimpl.UnknownFields diff --git a/src/runtime/vendor/modules.txt b/src/runtime/vendor/modules.txt index 7ea2921ee7..48f195b28f 100644 --- a/src/runtime/vendor/modules.txt +++ b/src/runtime/vendor/modules.txt @@ -720,8 +720,8 @@ google.golang.org/grpc/serviceconfig google.golang.org/grpc/stats google.golang.org/grpc/status google.golang.org/grpc/tap -# google.golang.org/protobuf v1.36.7 -## explicit; go 1.22 +# google.golang.org/protobuf v1.36.11 +## explicit; go 1.23 google.golang.org/protobuf/encoding/protodelim google.golang.org/protobuf/encoding/protojson google.golang.org/protobuf/encoding/prototext diff --git a/tools/kata/README.md b/tools/kata/README.md index f670fdbecc..1583c1729b 100644 --- a/tools/kata/README.md +++ b/tools/kata/README.md @@ -4,6 +4,9 @@ Kata helpers live here. - `run_kata.sh`: provides predefined `smoke`, `pass`, and `workload` Kata tasks - `kata_env.sh`: provides `install` and `check` for the shared Kata environment lifecycle - `kata_services.sh`: provides `start`, `stop`, and `status` for the background Kata smoke-test services +- `interactive_doc_test.py`: replays the documented `docker run -it` and + inner `nerdctl run -it` flows with `pexpect` for both the `asterinas/kata` + and `asterinas/asterinas` images - `check_overlayfs.sh`: probes whether the current host-side backing filesystem can support overlayfs `upperdir` and `workdir` for local Kata runs - `config/`: repo-owned Kata, CNI, `containerd`, and smoke-test config files used by the scripts @@ -26,6 +29,10 @@ Kata helpers live here. - Legacy `KATA_ALPINE_*` overrides still map to the new `KATA_TEST_*` names. - The default workload still pulls Alpine and runs `cat /etc/alpine-release`, but the image, in-container command, and output check are now script-configurable. +- `interactive_doc_test.py` uses `KATA_DOC_TEST_WORKLOAD_IMAGE`, which defaults + to `docker.io/alpine:latest`. For local validation in environments where + Docker Hub is unreachable, you can temporarily switch it to + `docker.1ms.run/alpine:latest`. ## Local virtio-fs note diff --git a/tools/kata/interactive_doc_test.py b/tools/kata/interactive_doc_test.py new file mode 100644 index 0000000000..3ce12ab0db --- /dev/null +++ b/tools/kata/interactive_doc_test.py @@ -0,0 +1,508 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import os +import pathlib +import re +import shlex +import subprocess +import sys +import uuid + +import pexpect + + +OUTER_PROMPT = "PEXPECT_OUTER> " +GUEST_PROMPT = "PEXPECT_GUEST> " +DEFAULT_KATA_IMAGE = "asterinas/kata:0.17.2-20260407" +DEFAULT_ASTERINAS_IMAGE = "asterinas/asterinas:0.17.2-20260407" +DEFAULT_WORKLOAD_IMAGE = "docker.io/alpine:latest" +REPO_ROOT = pathlib.Path(__file__).resolve().parents[2] +ANSI_ESCAPE_RE = re.compile(r"\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") + + +class ScenarioError(RuntimeError): + pass + + +class TeeWriter: + def __init__(self, *streams) -> None: + self.streams = streams + + def write(self, data: str) -> None: + for stream in self.streams: + stream.write(data) + stream.flush() + + def flush(self) -> None: + for stream in self.streams: + stream.flush() + + +def strip_terminal_control_sequences(text: str) -> str: + return ANSI_ESCAPE_RE.sub("", text).replace("\r", "") + + +def clean_command_output(command: str, output: str) -> str: + cleaned_lines: list[str] = [] + + for raw_line in strip_terminal_control_sequences(output).splitlines(): + line = raw_line.strip() + if not line: + continue + + if line.startswith(OUTER_PROMPT.strip()): + line = line[len(OUTER_PROMPT.strip()) :].strip() + elif line.startswith(GUEST_PROMPT.strip()): + line = line[len(GUEST_PROMPT.strip()) :].strip() + + if not line: + continue + + if line == command: + continue + if line == "'": + continue + if line == "status=$?": + continue + if line.startswith("printf '__DOC_TEST_EXIT__"): + continue + if line.startswith("__DOC_TEST_EXIT__"): + continue + + cleaned_lines.append(line) + + return "\n".join(cleaned_lines).strip() + + +def outer_docker_env_args() -> list[str]: + args = ["-e", "TERM=dumb"] + passthrough_vars = ( + "KATA_FORCE_APT", + "KATA_PAYLOAD_IMAGE", + "KATA_STATIC_TARBALL_RELEASE_REPO", + "KATA_STATIC_TARBALL_SHA256", + "KATA_STATIC_TARBALL_SHA256_URL", + "KATA_STATIC_TARBALL_URL", + "KATA_VERSION", + "NERDCTL_VERSION", + ) + + for env_name in passthrough_vars: + env_value = os.environ.get(env_name) + if env_value: + args.extend(["-e", f"{env_name}={env_value}"]) + + return args + + +def outer_docker_device_args() -> list[str]: + args: list[str] = [] + required_devices = ( + "/dev/kvm", + "/dev/vhost-net", + "/dev/vhost-vsock", + ) + optional_devices = ( + "/dev/vsock", + ) + + for device_path in required_devices: + if not pathlib.Path(device_path).exists(): + raise ScenarioError(f"Required device is missing on the host: {device_path}") + args.extend(["--device", device_path]) + + for device_path in optional_devices: + if pathlib.Path(device_path).exists(): + args.extend(["--device", device_path]) + + return args + + +def kernel_developer_cache_mount_args() -> list[str]: + args: list[str] = [] + cache_mounts = ( + ("KATA_DOC_TEST_NIX_DIR", "/nix"), + ("KATA_DOC_TEST_CARGO_HOME", "/root/.cargo"), + ("KATA_DOC_TEST_RUSTUP_HOME", "/root/.rustup"), + ) + + for env_name, container_path in cache_mounts: + host_path = os.environ.get(env_name) + if not host_path: + continue + + host_dir = pathlib.Path(host_path) + host_dir.mkdir(parents=True, exist_ok=True) + args.extend(["-v", f"{host_dir.resolve()}:{container_path}"]) + + return args + + +class DockerShell: + def __init__(self, name: str, docker_args: list[str], transcript_path: pathlib.Path) -> None: + self.name = name + self.docker_args = docker_args + self.transcript_path = transcript_path + self.child: pexpect.spawn | None = None + self.log_handle = None + self.prompt = OUTER_PROMPT + self.stream_output = os.environ.get("KATA_DOC_TEST_STREAM_OUTPUT", "1") not in {"0", "false", "FALSE", "no", "NO"} + + def start(self) -> "DockerShell": + self.transcript_path.parent.mkdir(parents=True, exist_ok=True) + self.log_handle = self.transcript_path.open("w", encoding="utf-8") + subprocess.run( + ["docker", "rm", "-f", self.name], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + self.child = pexpect.spawn( + "docker", + self.docker_args, + encoding="utf-8", + codec_errors="replace", + echo=False, + timeout=60, + ) + if self.stream_output: + self.child.logfile_read = TeeWriter(self.log_handle, sys.stdout) + else: + self.child.logfile_read = self.log_handle + self.child.setwinsize(60, 200) + self.child.expect(r"[#$] ", timeout=120) + self.set_prompt(OUTER_PROMPT) + return self + + def close(self) -> None: + if self.child is not None: + self.child.close(force=True) + self.child = None + subprocess.run( + ["docker", "rm", "-f", self.name], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + if self.log_handle is not None: + self.log_handle.close() + self.log_handle = None + + def set_prompt(self, prompt: str) -> None: + assert self.child is not None + self.child.sendline(f"export PS1={shlex.quote(prompt)}") + self.child.expect(re.escape(prompt), timeout=30) + self.prompt = prompt + + def run(self, command: str, timeout: int = 300, check: bool = True) -> str: + assert self.child is not None + marker = f"__DOC_TEST_EXIT__{uuid.uuid4().hex}__:" + wrapped_command = f"{command}; __doc_test_status=$?; printf '{marker}%s\\n' \"$__doc_test_status\"" + self.child.sendline(wrapped_command) + self.child.expect(re.compile(re.escape(marker) + r"(\d+)\r?\n"), timeout=timeout) + output = clean_command_output(wrapped_command, self.child.before) + status = int(self.child.match.group(1)) + self.child.expect(re.escape(self.prompt), timeout=60) + if check and status != 0: + raise ScenarioError(f"Command failed with exit code {status}: {command}\n{output}") + return output + + def enter_guest(self, command: str, timeout: int = 900) -> None: + assert self.child is not None + self.child.sendline(command) + match_index = self.child.expect([re.escape(OUTER_PROMPT), r"[#$] "], timeout=timeout) + if match_index == 0: + self.prompt = OUTER_PROMPT + failure_output = clean_command_output(command, self.child.before) + raise ScenarioError(f"Guest shell did not start successfully for command: {command}\n{failure_output}") + self.set_prompt(GUEST_PROMPT) + + def exit_guest(self) -> None: + assert self.child is not None + self.child.sendline("exit") + self.child.expect(re.escape(OUTER_PROMPT), timeout=120) + self.prompt = OUTER_PROMPT + + +def require_paths(paths: list[pathlib.Path]) -> None: + missing_paths = [str(path) for path in paths if not path.exists()] + if missing_paths: + raise ScenarioError(f"Missing required path(s): {', '.join(missing_paths)}") + + +def announce(message: str) -> None: + print(message, flush=True) + + +def dump_kata_debug_logs(shell: DockerShell, context: str) -> None: + announce(f"[debug] dumping Kata/QEMU logs after {context}") + for log_path in ( + "/tmp/kata-console.log", + "/tmp/console.log", + "/tmp/kata-qemu-serial.log", + "/tmp/qemu-serial.log", + "/tmp/containerd.log", + "/tmp/kata-syslog.log", + ): + output = shell.run( + f'test -f {shlex.quote(log_path)} && {{ echo "--- {log_path} ---"; tail -n 200 {shlex.quote(log_path)}; }} || true', + check=False, + timeout=60, + ) + if output: + announce(output) + + +def log_active_kata_configs(shell: DockerShell, label: str) -> None: + announce(f"[{label}] active Kata default config:") + announce( + shell.run( + "readlink -f /opt/kata/share/defaults/kata-containers/configuration.toml", + timeout=60, + ) + ) + announce(f"[{label}] active runtime config:") + announce( + shell.run( + "readlink -f /etc/kata-containers/configuration.toml 2>/dev/null || echo /etc/kata-containers/configuration.toml", + timeout=60, + ) + ) + + +def run_guest_workload(shell: DockerShell, workload_image: str, host_proc_version: str, container_name: str = "foo") -> tuple[str, str]: + announce(f"[guest] launching workload container {container_name} from {workload_image}") + shell.run(f"nerdctl rm -f {shlex.quote(container_name)} >/dev/null 2>&1 || true", check=False) + try: + shell.enter_guest( + " ".join( + [ + "nerdctl", + "run", + "--cgroup-manager", + "cgroupfs", + "--net", + "none", + "--runtime", + "io.containerd.kata.v2", + "--name", + shlex.quote(container_name), + "-it", + shlex.quote(workload_image), + ] + ), + timeout=900, + ) + guest_proc_version = shell.run("cat /proc/version") + alpine_release = shell.run("cat /etc/alpine-release") + shell.exit_guest() + announce(f"[guest] removing workload container {container_name}") + shell.run(f"nerdctl rm -f {shlex.quote(container_name)}") + except Exception: + dump_kata_debug_logs(shell, f"failed nerdctl run for {container_name}") + raise + + if guest_proc_version.strip() == host_proc_version.strip(): + raise ScenarioError("Guest /proc/version unexpectedly matches the outer container /proc/version") + if not alpine_release.strip(): + raise ScenarioError("Expected a non-empty /etc/alpine-release inside the guest workload") + + return guest_proc_version.strip(), alpine_release.strip() + + +def run_end_user_scenario(args: argparse.Namespace) -> pathlib.Path: + outer_name = f"kata-doc-end-user-{uuid.uuid4().hex[:12]}" + transcript_path = args.log_dir / "end-user.transcript.log" + shell = DockerShell( + outer_name, + [ + "run", + "--rm", + "-it", + "--name", + outer_name, + "--cgroupns", + "host", + "--privileged", + *outer_docker_device_args(), + "--tmpfs", + "/tmp:exec,mode=1777,size=8g", + "--tmpfs", + "/var/lib/containerd:exec,mode=755,size=8g", + *outer_docker_env_args(), + args.kata_image, + "bash", + ], + transcript_path, + ).start() + + try: + announce(f"[end-user] using outer image: {args.kata_image}") + host_proc_version = shell.run("cat /proc/version") + shell.run("cd /root/asterinas") + announce("[end-user] starting Kata background services") + shell.run("./tools/kata/kata_services.sh start", timeout=300) + log_active_kata_configs(shell, "end-user") + status_output = shell.run("./tools/kata/kata_services.sh status") + if "Kata services are running." not in status_output: + raise ScenarioError(f"Unexpected service status output:\n{status_output}") + guest_proc_version, alpine_release = run_guest_workload(shell, args.workload_image, host_proc_version) + announce(f"[end-user] guest /proc/version: {guest_proc_version}") + announce(f"[end-user] alpine release: {alpine_release}") + announce("[end-user] stopping Kata background services") + shell.run("./tools/kata/kata_services.sh stop", timeout=300) + return transcript_path + finally: + shell.close() + + +def run_kernel_developer_scenario(args: argparse.Namespace) -> pathlib.Path: + require_paths([args.repo_dir, args.asterinas_dir]) + outer_name = f"kata-doc-kernel-dev-{uuid.uuid4().hex[:12]}" + transcript_path = args.log_dir / "kernel-developer.transcript.log" + shell = DockerShell( + outer_name, + [ + "run", + "--rm", + "-it", + "--name", + outer_name, + "--cgroupns", + "host", + "--privileged", + *outer_docker_device_args(), + "--tmpfs", + "/tmp:exec,mode=1777,size=8g", + "--tmpfs", + "/var/lib/containerd:exec,mode=755,size=8g", + *outer_docker_env_args(), + *kernel_developer_cache_mount_args(), + "-v", + f"{args.asterinas_dir.resolve()}:/root/asterinas", + "-v", + f"{args.repo_dir.resolve()}:/root/kata-containers:ro", + "-w", + "/root/kata-containers", + args.asterinas_image, + "bash", + ], + transcript_path, + ).start() + + try: + announce(f"[kernel-developer] using outer image: {args.asterinas_image}") + announce(f"[kernel-developer] mounted Asterinas tree: {args.asterinas_dir}") + host_proc_version = shell.run("cat /proc/version") + shell.run("test -d /root/kata-containers/tools/kata") + announce("[kernel-developer] running tools/kata/kata_env.sh install") + shell.run("./tools/kata/kata_env.sh install", timeout=3600) + announce("[kernel-developer] starting Kata background services") + shell.run("/root/kata-containers/tools/kata/kata_services.sh start", timeout=300) + log_active_kata_configs(shell, "kernel-developer") + status_output = shell.run("/root/kata-containers/tools/kata/kata_services.sh status") + if "Kata services are running." not in status_output: + raise ScenarioError(f"Unexpected service status output:\n{status_output}") + packaged_guest_proc_version, packaged_alpine_release = run_guest_workload( + shell, + args.workload_image, + host_proc_version, + "foo", + ) + announce(f"[kernel-developer] packaged guest /proc/version: {packaged_guest_proc_version}") + announce(f"[kernel-developer] packaged alpine release: {packaged_alpine_release}") + announce("[kernel-developer] building local kernel with make kernel BOOT_METHOD=qemu-direct") + shell.run("cd /root/asterinas && make kernel BOOT_METHOD=qemu-direct", timeout=7200) + shell.run("test -f /root/asterinas/target/osdk/aster-kernel-osdk-bin.qemu_elf") + announce("[kernel-developer] switching Kata to the locally built kernel") + shell.run( + "sed -i 's#^kernel = \".*\"#kernel = \"/root/asterinas/target/osdk/aster-kernel-osdk-bin.qemu_elf\"#' " + "/etc/kata-containers/configuration.toml" + ) + shell.run( + "grep -F 'kernel = \"/root/asterinas/target/osdk/aster-kernel-osdk-bin.qemu_elf\"' " + "/etc/kata-containers/configuration.toml" + ) + local_guest_proc_version, local_alpine_release = run_guest_workload( + shell, + args.workload_image, + host_proc_version, + "foo", + ) + announce(f"[kernel-developer] local-kernel guest /proc/version: {local_guest_proc_version}") + announce(f"[kernel-developer] local-kernel alpine release: {local_alpine_release}") + announce("[kernel-developer] stopping Kata background services") + shell.run("/root/kata-containers/tools/kata/kata_services.sh stop", timeout=300) + return transcript_path + finally: + shell.close() + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Replay the documented Asterinas Kata flows with pexpect") + parser.add_argument( + "--scenario", + choices=["end-user", "kernel-developer", "all"], + default=os.environ.get("KATA_DOC_TEST_SCENARIO", "all"), + ) + parser.add_argument( + "--kata-image", + default=os.environ.get("KATA_DOC_TEST_KATA_IMAGE", DEFAULT_KATA_IMAGE), + ) + parser.add_argument( + "--asterinas-image", + default=os.environ.get("KATA_DOC_TEST_ASTERINAS_IMAGE", DEFAULT_ASTERINAS_IMAGE), + ) + parser.add_argument( + "--workload-image", + default=os.environ.get("KATA_DOC_TEST_WORKLOAD_IMAGE", DEFAULT_WORKLOAD_IMAGE), + ) + parser.add_argument( + "--repo-dir", + type=pathlib.Path, + default=pathlib.Path(os.environ.get("KATA_DOC_TEST_REPO_DIR", str(REPO_ROOT))), + ) + parser.add_argument( + "--asterinas-dir", + type=pathlib.Path, + default=pathlib.Path(os.environ.get("KATA_DOC_TEST_ASTERINAS_DIR", str(pathlib.Path.home() / "asterinas"))), + ) + parser.add_argument( + "--log-dir", + type=pathlib.Path, + default=pathlib.Path(os.environ.get("KATA_DOC_TEST_LOG_DIR", "/tmp/asterinas-kata-doc-tests")), + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + args.log_dir.mkdir(parents=True, exist_ok=True) + transcripts: list[pathlib.Path] = [] + + try: + if args.scenario in {"end-user", "all"}: + transcripts.append(run_end_user_scenario(args)) + if args.scenario in {"kernel-developer", "all"}: + transcripts.append(run_kernel_developer_scenario(args)) + except Exception as error: + announce(f"interactive_doc_test failed: {error}") + if transcripts: + announce("available transcript(s):") + for transcript in transcripts: + announce(f" - {transcript}") + else: + announce(f"transcript directory: {args.log_dir}") + return 1 + + announce("interactive_doc_test passed") + for transcript in transcripts: + announce(f"transcript: {transcript}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/kata/kata_env.sh b/tools/kata/kata_env.sh index 69c6bd632f..8c868b4e80 100755 --- a/tools/kata/kata_env.sh +++ b/tools/kata/kata_env.sh @@ -431,23 +431,20 @@ install_kata_from_payload_image() { install_kata_from_static_tarball() { source_marker_path="${KATA_INSTALL_SOURCE_MARKER:-/opt/kata/.kata-install-source}" static_tarball_url="$(resolve_static_tarball_url)" - extract_dir="${KATA_STATIC_EXTRACT_DIR:-/tmp/kata-static-extract}" static_tarball_sha256="$(resolve_static_tarball_sha256)" static_tarball_path="$(prepare_cached_static_tarball)" - rm -rf "${extract_dir}" - install -d -m 0755 "${extract_dir}" - tar --zstd -xf "${static_tarball_path}" -C "${extract_dir}" - - test -d "${extract_dir}/opt/kata" + echo "Installing Kata static tarball from ${static_tarball_url}" rm -rf /opt/kata - cp -a "${extract_dir}/opt/kata" /opt/ + tar --zstd -xf "${static_tarball_path}" -C / + test -d /opt/kata { printf 'static-tarball-url %s\n' "${static_tarball_url}" printf 'static-tarball-sha256 %s\n' "${static_tarball_sha256}" } > "${source_marker_path}" test -x /opt/kata/bin/kata-runtime test -x /opt/kata/bin/containerd-shim-kata-v2 + echo "Installed Kata static tarball into /opt/kata" } patch_qemu_config_for_asterinas() { @@ -545,9 +542,11 @@ wait_for_containerd_ready() { } run_install_task() { + echo "Installing required distro packages" install_required_packages if need_nerdctl_install; then + echo "Installing nerdctl ${NERDCTL_VERSION}" download_release_asset \ /tmp/nerdctl.tgz \ "https://github.com/containerd/nerdctl/releases/download/${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION#v}-linux-amd64.tar.gz" @@ -555,6 +554,7 @@ run_install_task() { fi if should_install_crictl && need_crictl_install; then + echo "Installing crictl ${CRICTL_VERSION}" download_release_asset \ /tmp/crictl.tgz \ "https://github.com/kubernetes-sigs/cri-tools/releases/download/${CRICTL_VERSION}/crictl-${CRICTL_VERSION}-linux-amd64.tar.gz" @@ -563,10 +563,13 @@ run_install_task() { if need_kata_install; then if [ -n "${KATA_ASTERINAS_KERNEL_PATH:-}" ] && [ -f "${KATA_ASTERINAS_KERNEL_PATH}" ]; then + echo "Installing Kata with local Asterinas kernel overlay" install_kata_from_asterinas_kernel_overlay elif [ -n "${KATA_STATIC_TARBALL_URL:-}" ] || [ -n "${KATA_STATIC_TARBALL_RELEASE_REPO:-}" ]; then + echo "Installing Kata from static tarball" install_kata_from_static_tarball else + echo "Installing Kata from payload image" install_kata_from_payload_image fi fi @@ -574,6 +577,7 @@ run_install_task() { install -d -m 0755 /usr/local/bin ln -sf /opt/kata/bin/kata-runtime /usr/local/bin/kata-runtime ln -sf /opt/kata/bin/containerd-shim-kata-v2 /usr/local/bin/containerd-shim-kata-v2 + echo "Kata install task completed" } run_check_task() { diff --git a/tools/packaging/asterinas-kata/Dockerfile b/tools/packaging/asterinas-kata/Dockerfile index f6773e0e36..647ded2d13 100644 --- a/tools/packaging/asterinas-kata/Dockerfile +++ b/tools/packaging/asterinas-kata/Dockerfile @@ -3,6 +3,7 @@ FROM ${ASTERINAS_BASE_IMAGE} ARG KATA_STATIC_TARBALL_RELEASE_REPO=kata-containers/kata-containers ARG KATA_STATIC_TARBALL_URL= +ARG KATA_STATIC_TARBALL_SHA256= ARG ASTERINAS_RELEASE_VERSION= ARG ASTERINAS_DOCKER_IMAGE_VERSION= @@ -12,6 +13,7 @@ COPY tools/kata /root/asterinas/tools/kata RUN KATA_STATIC_TARBALL_RELEASE_REPO="${KATA_STATIC_TARBALL_RELEASE_REPO}" \ KATA_STATIC_TARBALL_URL="${KATA_STATIC_TARBALL_URL}" \ + KATA_STATIC_TARBALL_SHA256="${KATA_STATIC_TARBALL_SHA256}" \ bash /root/asterinas/tools/kata/kata_env.sh install RUN printf '%s\n' "${ASTERINAS_RELEASE_VERSION}" > /root/asterinas/.asterinas-version && \ diff --git a/tools/packaging/release/build-asterinas-release.sh b/tools/packaging/release/build-asterinas-release.sh index 62a9a31d7a..c426c765e7 100644 --- a/tools/packaging/release/build-asterinas-release.sh +++ b/tools/packaging/release/build-asterinas-release.sh @@ -18,6 +18,7 @@ FORCE_RUNTIME_BUILD="${FORCE_RUNTIME_BUILD:-false}" ASTERINAS_REPOSITORY="${ASTERINAS_REPOSITORY:-}" ASTERINAS_REF="${ASTERINAS_REF:-}" +ASTERINAS_COMMIT="${ASTERINAS_COMMIT:-}" ASTERINAS_BUILDER_IMAGE="${ASTERINAS_BUILDER_IMAGE:-}" ASTERINAS_KERNEL="${ASTERINAS_KERNEL:-}" ASTERINAS_TDX_KERNEL="${ASTERINAS_TDX_KERNEL:-}" @@ -380,8 +381,11 @@ write_release_notes() { printf -- '- Kata version: `%s`\n' "${VERSION}" printf -- '- Architecture: `%s`\n' "${ARCHITECTURE}" printf -- '- Base tarball: `%s`\n' "${BASE_TARBALL_URL}" - printf -- '- Kata commit: `%s`\n' "${KATA_COMMIT}" + printf -- '- kata-containers commit: `%s`\n' "${KATA_COMMIT}" printf -- '- Asterinas repo/ref: `%s@%s`\n' "${ASTERINAS_REPOSITORY}" "${ASTERINAS_REF}" + if [ -n "${ASTERINAS_COMMIT}" ]; then + printf -- '- Asterinas commit: `%s`\n' "${ASTERINAS_COMMIT}" + fi printf -- '- Asterinas builder image: `%s`\n' "${ASTERINAS_BUILDER_IMAGE}" printf -- '- Guest kernel: `%s`\n' "${ASTERINAS_KERNEL_PATH}" if [ -n "${ASTERINAS_TDX_KERNEL}" ]; then