From eab029ee21b7eefe2f14b59f7126771cc523f9c1 Mon Sep 17 00:00:00 2001 From: Allison Thackston <73732028+althack@users.noreply.github.com> Date: Sat, 28 Mar 2026 12:22:56 -0700 Subject: [PATCH 1/2] Add gz template --- .github/actions/smoke-test/action.yaml | 23 ++++++ .github/actions/smoke-test/build.sh | 55 ++++++++++++++ .github/actions/smoke-test/test.sh | 13 ++++ .github/workflows/release.yaml | 23 ++++++ .github/workflows/test-pr.yaml | 73 +++++++++++++++++++ .gitignore | 1 + .vscode/tasks.json | 45 ++++++++++++ README.md | 14 +++- .../src/gz/.devcontainer/devcontainer.json | 26 +++++++ templates/src/gz/README.md | 10 +++ templates/src/gz/devcontainer-template.json | 43 +++++++++++ 11 files changed, 324 insertions(+), 2 deletions(-) create mode 100644 .github/actions/smoke-test/action.yaml create mode 100755 .github/actions/smoke-test/build.sh create mode 100755 .github/actions/smoke-test/test.sh create mode 100644 .github/workflows/release.yaml create mode 100644 .github/workflows/test-pr.yaml create mode 100644 .gitignore create mode 100644 .vscode/tasks.json create mode 100644 templates/src/gz/.devcontainer/devcontainer.json create mode 100644 templates/src/gz/README.md create mode 100644 templates/src/gz/devcontainer-template.json diff --git a/.github/actions/smoke-test/action.yaml b/.github/actions/smoke-test/action.yaml new file mode 100644 index 0000000..76ed052 --- /dev/null +++ b/.github/actions/smoke-test/action.yaml @@ -0,0 +1,23 @@ +name: 'Smoke test' +description: Run a smoke test on built templates +inputs: + template: + description: 'Template id under templates/src to test' + required: true + +runs: + using: composite + steps: + - name: Checkout main + id: checkout_release + uses: actions/checkout@v6 + + - name: Build template + id: build_template + shell: bash + run: ${{ github.action_path }}/build.sh ${{ inputs.template }} + + - name: Test template + id: test_template + shell: bash + run: ${{ github.action_path }}/test.sh ${{ inputs.template }} diff --git a/.github/actions/smoke-test/build.sh b/.github/actions/smoke-test/build.sh new file mode 100755 index 0000000..f7cb9b0 --- /dev/null +++ b/.github/actions/smoke-test/build.sh @@ -0,0 +1,55 @@ +#!/bin/bash +TEMPLATE_ID="$1" + +set -e + +shopt -s dotglob + +SRC_DIR="/tmp/${TEMPLATE_ID}" +cp -R "templates/src/${TEMPLATE_ID}" "${SRC_DIR}" + +pushd "${SRC_DIR}" + +# Configure templates only if `devcontainer-template.json` contains the `options` property. +OPTION_PROPERTY=( $(jq -r '.options' devcontainer-template.json) ) + +if [ "${OPTION_PROPERTY}" != "" ] && [ "${OPTION_PROPERTY}" != "null" ] ; then + OPTIONS=( $(jq -r '.options | keys[]' devcontainer-template.json) ) + + if [ "${OPTIONS[0]}" != "" ] && [ "${OPTIONS[0]}" != "null" ] ; then + echo "(!) Configuring template options for '${TEMPLATE_ID}'" + for OPTION in "${OPTIONS[@]}" + do + OPTION_KEY="\${templateOption:$OPTION}" + OPTION_VALUE=$(jq -r ".options | .${OPTION} | .default" devcontainer-template.json) + + if [ "${OPTION_VALUE}" = "" ] || [ "${OPTION_VALUE}" = "null" ] ; then + echo "Template '${TEMPLATE_ID}' is missing a default value for option '${OPTION}'" + exit 1 + fi + + echo "(!) Replacing '${OPTION_KEY}' with '${OPTION_VALUE}'" + OPTION_VALUE_ESCAPED=$(sed -e 's/[]\/$*.^[]/\\&/g' <<<"${OPTION_VALUE}") + find ./ -type f -print0 | xargs -0 sed -i "s/${OPTION_KEY}/${OPTION_VALUE_ESCAPED}/g" + done + fi +fi + +popd + +TEST_DIR="test/${TEMPLATE_ID}" +if [ -d "${TEST_DIR}" ] ; then + echo "(*) Copying test folder" + DEST_DIR="${SRC_DIR}/test-project" + mkdir -p ${DEST_DIR} + cp -Rp ${TEST_DIR}/* ${DEST_DIR} + cp -Rp test/test-utils/* ${DEST_DIR} +fi + +export DOCKER_BUILDKIT=1 +echo "(*) Installing @devcontainer/cli" +npm install -g @devcontainers/cli + +echo "Building Dev Container" +ID_LABEL="test-container=${TEMPLATE_ID}" +devcontainer up --id-label ${ID_LABEL} --workspace-folder "${SRC_DIR}" diff --git a/.github/actions/smoke-test/test.sh b/.github/actions/smoke-test/test.sh new file mode 100755 index 0000000..5de1a2c --- /dev/null +++ b/.github/actions/smoke-test/test.sh @@ -0,0 +1,13 @@ +#!/bin/bash +TEMPLATE_ID="$1" +set -e + +SRC_DIR="/tmp/${TEMPLATE_ID}" +echo "Running Smoke Test" + +ID_LABEL="test-container=${TEMPLATE_ID}" +devcontainer exec --workspace-folder "${SRC_DIR}" --id-label ${ID_LABEL} /bin/sh -c 'set -e && if [ -f "test-project/test.sh" ]; then cd test-project && if [ "$(id -u)" = "0" ]; then chmod +x test.sh; else sudo chmod +x test.sh; fi && ./test.sh; else ls -a; fi' + +# Clean up +docker rm -f $(docker container ls -f "label=${ID_LABEL}" -q) +rm -rf "${SRC_DIR}" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..00b3feb --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,23 @@ +name: "Release Dev Container Templates" +on: + workflow_dispatch: + +jobs: + deploy: + if: ${{ github.ref == 'refs/heads/main' }} + runs-on: ubuntu-latest + permissions: + packages: write + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v6 + + - name: "Publish Templates" + uses: devcontainers/action@v1 + with: + publish-templates: "true" + base-path-to-templates: "./templates/src" + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml new file mode 100644 index 0000000..db4f7fb --- /dev/null +++ b/.github/workflows/test-pr.yaml @@ -0,0 +1,73 @@ +name: "CI - Test Templates" +on: + pull_request: + +jobs: + smoke-test: + runs-on: ubuntu-latest + permissions: + # Give the default GITHUB_TOKEN write permission to commit and push the + # added or changed files to the repository. + contents: write + steps: + - uses: actions/checkout@v6 + + - name: Smoke test for "gz" + id: smoke_test + uses: ./.github/actions/smoke-test + with: + template: "gz" + + gen-docs: + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + runs-on: ubuntu-latest + permissions: + # Give the default GITHUB_TOKEN write permission to commit and push the + # added or changed files to the repository. + contents: write + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.head_ref }} + token: ${{ secrets.CI_BOT_TOKEN }} + + - name: "Publish Templates" + uses: devcontainers/action@v1 + with: + publish-templates: "false" + base-path-to-templates: "./templates/src" + generate-docs: "true" + + - uses: stefanzweifel/git-auto-commit-action@v7 + with: + commit_message: Update generated docs + + complete: + needs: + - gen-docs + - smoke-test + if: always() + runs-on: ubuntu-latest + steps: + - name: Verify required jobs succeeded + env: + SMOKE_TEST_RESULT: ${{ needs.smoke-test.result }} + GEN_DOCS_RESULT: ${{ needs.gen-docs.result }} + run: | + set -euo pipefail + + echo "smoke_test: ${SMOKE_TEST_RESULT}" + echo "gen_docs: ${GEN_DOCS_RESULT}" + + if [[ "${SMOKE_TEST_RESULT}" != "success" ]]; then + echo "Required job 'smoke_test' did not succeed." + exit 1 + fi + + # gen_docs is skipped for fork PRs by design; treat that as acceptable. + if [[ "${GEN_DOCS_RESULT}" != "success" && "${GEN_DOCS_RESULT}" != "skipped" ]]; then + echo "Required job 'gen_docs' failed or was cancelled." + exit 1 + fi + + echo "All required jobs completed successfully." diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3018b3a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.tmp/ diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..1bb7bbe --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,45 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Local Smoke Test GZ: Build", + "type": "process", + "command": ".github/actions/smoke-test/build.sh", + "args": [ + "gz" + ], + "options": { + "cwd": "${workspaceFolder}", + "env": { + "TEMPLATE_ARGS": "{\"distro\":\"jetty\",\"imageVariant\":\"base\"}" + } + }, + "problemMatcher": [] + }, + { + "label": "Local Smoke Test GZ: Assert", + "dependsOrder": "sequence", + "dependsOn": [ + "Local Smoke Test GZ: Build" + ], + "type": "process", + "command": ".github/actions/smoke-test/test.sh", + "args": [ + "gz" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Local Smoke Test GZ", + "dependsOrder": "sequence", + "dependsOn": [ + "Local Smoke Test GZ: Build", + "Local Smoke Test GZ: Assert" + ], + "problemMatcher": [] + } + ] +} diff --git a/README.md b/README.md index 89877b9..fcfb820 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,12 @@ -# ros2-devcontainer-templates -ROS2 devcontainer templates +# devcontainers + +Devcontainer templates repository focused on robotics and simulation. + +## Included template + +- `templates/src/gz` + +## Workflows + +- `.github/workflows/test-pr.yaml`: smoke tests template changes on pull requests +- `.github/workflows/release.yaml`: publishes templates to GHCR on manual dispatch from `main` diff --git a/templates/src/gz/.devcontainer/devcontainer.json b/templates/src/gz/.devcontainer/devcontainer.json new file mode 100644 index 0000000..13790e1 --- /dev/null +++ b/templates/src/gz/.devcontainer/devcontainer.json @@ -0,0 +1,26 @@ +{ + "name": "Gazebo", + "image": "althack/gz:${templateOption:distro}-${templateOption:imageVariant}", + "remoteUser": "ros", + "updateRemoteUserUID": true, + "containerEnv": { + "GZ_VERSION": "${templateOption:distro}" + }, + "runArgs": [ + "--network=host", + "--ipc=host", + "--cap-add=SYS_PTRACE", + "--security-opt=seccomp:unconfined", + "--security-opt=apparmor:unconfined" + ], + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "ms-python.python", + "twxs.cmake", + "redhat.vscode-yaml" + ] + } + } +} diff --git a/templates/src/gz/README.md b/templates/src/gz/README.md new file mode 100644 index 0000000..defef9a --- /dev/null +++ b/templates/src/gz/README.md @@ -0,0 +1,10 @@ +# Gazebo + +Gazebo (`gz`) devcontainer template. + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| distro | Gazebo distribution to use. | string | jetty | +| imageVariant | Container image variant to use for this Gazebo distro. | string | base | diff --git a/templates/src/gz/devcontainer-template.json b/templates/src/gz/devcontainer-template.json new file mode 100644 index 0000000..8451012 --- /dev/null +++ b/templates/src/gz/devcontainer-template.json @@ -0,0 +1,43 @@ +{ + "id": "gz", + "version": "0.1.0", + "name": "Gazebo", + "description": "Gazebo development environment.", + "documentationURL": "https://github.com/athackst/devcontainer-templates/tree/main/templates/src/gz", + "licenseURL": "https://github.com/athackst/devcontainer-templates/blob/main/LICENSE", + "publisher": "althack", + "keywords": [ + "gazebo", + "gz", + "robotics", + "ubuntu", + "docker" + ], + "platforms": [ + "Gazebo", + "Ubuntu", + "C++", + "Python" + ], + "options": { + "distro": { + "type": "string", + "description": "Gazebo distribution to use.", + "proposals": [ + "jetty", + "ionic", + "harmonic" + ], + "default": "jetty" + }, + "imageVariant": { + "type": "string", + "description": "Container image variant to use for this Gazebo distro.", + "proposals": [ + "base", + "dev" + ], + "default": "base" + } + } +} From ebe594f465416f1cb8e697b4df8b334011c057b3 Mon Sep 17 00:00:00 2001 From: althack <73732028+althack@users.noreply.github.com> Date: Sat, 28 Mar 2026 19:55:34 +0000 Subject: [PATCH 2/2] Update generated docs --- templates/src/gz/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/templates/src/gz/README.md b/templates/src/gz/README.md index defef9a..cf1a904 100644 --- a/templates/src/gz/README.md +++ b/templates/src/gz/README.md @@ -1,6 +1,7 @@ -# Gazebo -Gazebo (`gz`) devcontainer template. +# Gazebo (gz) + +Gazebo development environment. ## Options @@ -8,3 +9,9 @@ Gazebo (`gz`) devcontainer template. |-----|-----|-----|-----| | distro | Gazebo distribution to use. | string | jetty | | imageVariant | Container image variant to use for this Gazebo distro. | string | base | + + + +--- + +_Note: This file was auto-generated from the [devcontainer-template.json](https://github.com/althack/devcontainers/blob/main/templates/src/gz/devcontainer-template.json). Add additional notes to a `NOTES.md`._