From e198a0826ac264433c38b86a933ec62ae09bf858 Mon Sep 17 00:00:00 2001 From: philomagi Date: Thu, 11 Jun 2026 13:39:02 +0900 Subject: [PATCH 1/8] Add git-kura devcontainer feature --- .github/workflows/release.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5a322bb..9a5e725 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -22,6 +22,15 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Publish Features" + uses: devcontainers/action@1082abd5d2bf3a11abccba70eef98df068277772 # v1 + with: + publish-features: "true" + base-path-to-features: "./src" + generate-docs: "true" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Create PR for Documentation id: push_image_info env: From a2819b4c943e86913873034c882eb450b54c217b Mon Sep 17 00:00:00 2001 From: philomagi Date: Thu, 11 Jun 2026 13:40:15 +0900 Subject: [PATCH 2/8] Add git-kura devcontainer feature --- .github/workflows/devcontainer-feature.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/devcontainer-feature.yml diff --git a/.github/workflows/devcontainer-feature.yml b/.github/workflows/devcontainer-feature.yml new file mode 100644 index 0000000..5edf972 --- /dev/null +++ b/.github/workflows/devcontainer-feature.yml @@ -0,0 +1,22 @@ +name: "CI - Test Features" + +on: + pull_request: + paths: + - ".github/workflows/devcontainer-feature.yml" + - "src/git-kura/**" + - "test/git-kura/**" + - "test/test-utils/**" + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - name: Install Dev Container CLI + run: npm install -g @devcontainers/cli + + - name: Test git-kura Feature + run: devcontainer features test --features git-kura --skip-scenarios --base-image mcr.microsoft.com/devcontainers/base:ubuntu . From 38627c0c74fa9a0c4e46d4f868ecbee42e5a7a44 Mon Sep 17 00:00:00 2001 From: philomagi Date: Thu, 11 Jun 2026 13:40:28 +0900 Subject: [PATCH 3/8] Add git-kura devcontainer feature --- src/git-kura/devcontainer-feature.json | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/git-kura/devcontainer-feature.json diff --git a/src/git-kura/devcontainer-feature.json b/src/git-kura/devcontainer-feature.json new file mode 100644 index 0000000..4d1f951 --- /dev/null +++ b/src/git-kura/devcontainer-feature.json @@ -0,0 +1,28 @@ +{ + "id": "git-kura", + "version": "0.1.0", + "name": "git-kura", + "description": "Installs git-kura, a keyed Git worktree resolver.", + "documentationURL": "https://github.com/tooppoo/devcontainer-features/tree/main/src/git-kura", + "licenseURL": "https://github.com/tooppoo/devcontainer-features/blob/main/LICENSE", + "options": { + "version": { + "type": "string", + "description": "git-kura release tag to install. Use latest to install the latest release.", + "default": "latest" + }, + "install_dir": { + "type": "string", + "description": "Directory where git-kura will be installed.", + "default": "/usr/local/bin" + }, + "require_signature": { + "type": "boolean", + "description": "Require cosign signature verification during installation.", + "default": false + } + }, + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ] +} From 96c18a0f78d0595602768e863bf2e56a2eee4a6c Mon Sep 17 00:00:00 2001 From: philomagi Date: Thu, 11 Jun 2026 13:40:46 +0900 Subject: [PATCH 4/8] Add git-kura devcontainer feature --- src/git-kura/install.sh | 76 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/git-kura/install.sh diff --git a/src/git-kura/install.sh b/src/git-kura/install.sh new file mode 100644 index 0000000..2022728 --- /dev/null +++ b/src/git-kura/install.sh @@ -0,0 +1,76 @@ +#!/bin/sh +set -eu + +REPO="tooppoo/git-kura" +INSTALLER_URL="https://raw.githubusercontent.com/${REPO}/main/install.sh" + +GIT_KURA_VERSION="${VERSION:-latest}" +INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}" +REQUIRE_SIGNATURE="${REQUIRE_SIGNATURE:-false}" + +echo_stderr() { + echo "$@" >&2 +} + +check_supported_distribution() { + if [ ! -r /etc/os-release ]; then + echo_stderr "Unsupported distribution: /etc/os-release was not found." + exit 1 + fi + + # shellcheck disable=SC1091 + . /etc/os-release + + distribution="${ID:-}" + distribution_like="${ID_LIKE:-}" + + case "${distribution} ${distribution_like}" in + *debian*|*ubuntu*) ;; + *) + echo_stderr "Unsupported distribution: git-kura Feature v0 supports Debian/Ubuntu based images only." + echo_stderr "Detected ID='${distribution}' ID_LIKE='${distribution_like}'." + exit 1 + ;; + esac +} + +install_packages() { + if ! command -v apt-get >/dev/null 2>&1; then + echo_stderr "Unsupported distribution: apt-get is required for git-kura Feature v0." + exit 1 + fi + + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y --no-install-recommends ca-certificates curl git tar + rm -rf /var/lib/apt/lists/* +} + +run_installer() { + set -- --install-dir "${INSTALL_DIR}" + + if [ "${GIT_KURA_VERSION}" != "latest" ]; then + set -- "$@" --version "${GIT_KURA_VERSION}" + fi + + case "${REQUIRE_SIGNATURE}" in + true|1|yes) + set -- "$@" --require-signature + ;; + false|0|no) + ;; + *) + echo_stderr "Invalid require_signature value: ${REQUIRE_SIGNATURE}. Use true or false." + exit 1 + ;; + esac + + echo "Installing git-kura from ${INSTALLER_URL}" + # Intentionally calls the upstream installer so release resolution, + # checksums, and optional signature verification stay owned by git-kura. + curl -fsSL "${INSTALLER_URL}" | sh -s -- "$@" +} + +check_supported_distribution +install_packages +run_installer From 7d4c9b7ad1460ccbc6436a55ae44e493ab5f1d0a Mon Sep 17 00:00:00 2001 From: philomagi Date: Thu, 11 Jun 2026 13:41:04 +0900 Subject: [PATCH 5/8] Add git-kura devcontainer feature --- src/git-kura/README.md | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/git-kura/README.md diff --git a/src/git-kura/README.md b/src/git-kura/README.md new file mode 100644 index 0000000..f0f379a --- /dev/null +++ b/src/git-kura/README.md @@ -0,0 +1,54 @@ +# git-kura (git-kura) + +Installs [git-kura](https://github.com/tooppoo/git-kura), a keyed Git worktree resolver. + +After this Feature is installed, `git-kura` is available on `PATH` and can also be used as a Git subcommand with `git kura ...`. + +## Example Usage + +```jsonc +{ + "features": { + "ghcr.io/tooppoo/devcontainer-features/git-kura:0": {} + } +} +``` + +Pin a specific git-kura binary release: + +```jsonc +{ + "features": { + "ghcr.io/tooppoo/devcontainer-features/git-kura:0": { + "version": "v0.0.3" + } + } +} +``` + +Install into a custom directory and require cosign verification: + +```jsonc +{ + "features": { + "ghcr.io/tooppoo/devcontainer-features/git-kura:0": { + "install_dir": "/usr/local/bin", + "require_signature": true + } + } +} +``` + +When `require_signature` is `true`, `cosign` must already be installed in the image. If it is missing, installation fails with an explicit error from the upstream installer. + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| version | git-kura release tag to install. Use latest to install the latest release. | string | latest | +| install_dir | Directory where git-kura will be installed. | string | /usr/local/bin | +| require_signature | Require cosign signature verification during installation. | boolean | false | + +## Supported Images + +This Feature currently supports Debian/Ubuntu based images. From c3b48ffa5df37bb02153138024d70fd971d7e4e2 Mon Sep 17 00:00:00 2001 From: philomagi Date: Thu, 11 Jun 2026 13:41:15 +0900 Subject: [PATCH 6/8] Add git-kura devcontainer feature --- test/git-kura/test.sh | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/git-kura/test.sh diff --git a/test/git-kura/test.sh b/test/git-kura/test.sh new file mode 100644 index 0000000..03631b0 --- /dev/null +++ b/test/git-kura/test.sh @@ -0,0 +1,41 @@ +#!/bin/bash +set -e + +FAILED=() + +echo_stderr() { + echo "$@" >&2 +} + +check() { + local label="$1" + shift + + echo + echo "Testing ${label}" + if "$@"; then + echo "Passed" + return 0 + fi + + echo_stderr "${label} check failed." + FAILED+=("${label}") + return 1 +} + +report_results() { + if [ ${#FAILED[@]} -ne 0 ]; then + echo_stderr + echo_stderr "Failed tests: ${FAILED[*]}" + exit 1 + fi + + echo + echo "All passed" +} + +check "git-kura is on PATH" command -v git-kura +check "git-kura help works" git-kura -h +check "git subcommand help works" git kura -h + +report_results From 1998d60d24c8bf60c9425ff957d7f684cc6041cd Mon Sep 17 00:00:00 2001 From: philomagi Date: Thu, 11 Jun 2026 13:34:17 +0900 Subject: [PATCH 7/8] Add git-kura devcontainer feature --- src/git-kura/install.sh | 0 test/git-kura/test.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 src/git-kura/install.sh mode change 100644 => 100755 test/git-kura/test.sh diff --git a/src/git-kura/install.sh b/src/git-kura/install.sh old mode 100644 new mode 100755 diff --git a/test/git-kura/test.sh b/test/git-kura/test.sh old mode 100644 new mode 100755 From 7c28fa4bfccf7fdefcc5206fa91adc319dc26c9c Mon Sep 17 00:00:00 2001 From: philomagi Date: Thu, 11 Jun 2026 13:59:24 +0900 Subject: [PATCH 8/8] remove samples --- .github/actions/smoke-test/action.yaml | 22 ---- .github/actions/smoke-test/build.sh | 55 ---------- .github/actions/smoke-test/test.sh | 13 --- .github/workflows/release.yaml | 11 +- .github/workflows/test-pr.yaml | 32 ------ README.md | 123 ++-------------------- src/color/.devcontainer/devcontainer.json | 18 ---- src/color/README.md | 17 --- src/color/devcontainer-template.json | 32 ------ src/hello/.devcontainer/Dockerfile | 4 - src/hello/.devcontainer/devcontainer.json | 21 ---- src/hello/README.md | 17 --- src/hello/devcontainer-template.json | 34 ------ test/color/test.sh | 10 -- test/hello/test.sh | 10 -- 15 files changed, 7 insertions(+), 412 deletions(-) delete mode 100644 .github/actions/smoke-test/action.yaml delete mode 100755 .github/actions/smoke-test/build.sh delete mode 100755 .github/actions/smoke-test/test.sh delete mode 100644 .github/workflows/test-pr.yaml delete mode 100644 src/color/.devcontainer/devcontainer.json delete mode 100644 src/color/README.md delete mode 100644 src/color/devcontainer-template.json delete mode 100644 src/hello/.devcontainer/Dockerfile delete mode 100644 src/hello/.devcontainer/devcontainer.json delete mode 100644 src/hello/README.md delete mode 100644 src/hello/devcontainer-template.json delete mode 100644 test/color/test.sh delete mode 100644 test/hello/test.sh diff --git a/.github/actions/smoke-test/action.yaml b/.github/actions/smoke-test/action.yaml deleted file mode 100644 index 3ee80e7..0000000 --- a/.github/actions/smoke-test/action.yaml +++ /dev/null @@ -1,22 +0,0 @@ -name: 'Smoke test' -inputs: - template: - description: 'Template to test' - required: true - -runs: - using: composite - steps: - - name: Checkout main - id: checkout_release - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # 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 }} \ No newline at end of file diff --git a/.github/actions/smoke-test/build.sh b/.github/actions/smoke-test/build.sh deleted file mode 100755 index 60a5fff..0000000 --- a/.github/actions/smoke-test/build.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash -TEMPLATE_ID="$1" - -set -e - -shopt -s dotglob - -SRC_DIR="/tmp/${TEMPLATE_ID}" -cp -R "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 deleted file mode 100755 index 5de1a2c..0000000 --- a/.github/actions/smoke-test/test.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/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 index 9a5e725..3f1639e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,4 +1,4 @@ -name: "Release Dev Container & Generate Documentation" +name: "Release Dev Container Features & Generate Documentation" on: workflow_dispatch: @@ -13,15 +13,6 @@ jobs: steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - - name: "Publish Templates" - uses: devcontainers/action@1082abd5d2bf3a11abccba70eef98df068277772 # v1 - with: - publish-templates: "true" - base-path-to-templates: "./src" - generate-docs: "true" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: "Publish Features" uses: devcontainers/action@1082abd5d2bf3a11abccba70eef98df068277772 # v1 with: diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml deleted file mode 100644 index 600534e..0000000 --- a/.github/workflows/test-pr.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: "CI - Test Templates" -on: - pull_request: - -jobs: - detect-changes: - runs-on: ubuntu-latest - outputs: - templates: ${{ steps.filter.outputs.changes }} - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4 - id: filter - with: - filters: | - color: ./**/color/** - hello: ./**/hello/** - - test: - needs: [detect-changes] - runs-on: ubuntu-latest - continue-on-error: true - strategy: - matrix: - templates: ${{ fromJSON(needs.detect-changes.outputs.templates) }} - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - - - name: Smoke test for '${{ matrix.templates }}' - id: smoke_test - uses: ./.github/actions/smoke-test - with: - template: "${{ matrix.templates }}" diff --git a/README.md b/README.md index dacf5d7..f7d61c4 100644 --- a/README.md +++ b/README.md @@ -1,122 +1,11 @@ -# Dev Container Templates: Self Authoring Guide +# Dev Container Features -> This repo provides a starting point and example for creating your own custom [Dev Container Templates](https://containers.dev/implementors/templates), hosted for free on GitHub Container Registry. The example in this repository follows the [Dev Container Template distribution specification](https://containers.dev/implementors/templates-distribution/). -> -> To provide feedback on the distribution spec, please leave a comment [on spec issue #71](https://github.com/devcontainers/spec/issues/71). +This repository publishes Dev Container Features. -## Repo and Template Structure +## Features -This repository contains a _collection_ of two Templates - `hello` and `color`. These Templates serve as simple template implementations which helps containerize the project. Similar to the [`devcontainers/templates`](https://github.com/devcontainers/templates) repo, this repository has a `src` folder. Each Template has its own sub-folder, containing at least a `devcontainer-template.json` and `.devcontainer/devcontainer.json`. +- `git-kura`: installs `git-kura`, a keyed Git worktree resolver. -``` -├── src -│ ├── color -│ │ ├── devcontainer-template.json -│ │ └──| .devcontainer -│ │ └── devcontainer.json -│ ├── hello -│ │ ├── devcontainer-template.json -│ │ └──| .devcontainer -│ │ ├── devcontainer.json -│ │ └── Dockerfile -| ├── ... -│ │ ├── devcontainer-template.json -│ │ └──| .devcontainer -│ │ └── devcontainer.json -├── test -│ ├── color -│ │ └── test.sh -│ ├── hello -│ │ └── test.sh -│ └──test-utils -│ └── test-utils.sh -... -``` +## Publishing -### Options - -All available options for a Template should be declared in the `devcontainer-template.json`. The syntax for the `options` property can be found in the [devcontainer Template json properties reference](https://containers.dev/implementors/templates#devcontainer-templatejson-properties). - -For example, the `color` Template provides three possible options (`red`, `gold`, `green`), where the default value is set to "red". - -```jsonc -{ - // ... - "options": { - "favorite": { - "type": "string", - "description": "Choose your favorite color.", - "proposals": [ - "red", - "gold", - "green" - ], - "default": "red" - } - } -} -``` - -An [implementing tool](https://containers.dev/supporting#tools) will use the `options` property from [the documented Dev Container Template properties](https://containers.dev/implementors/templates#devcontainer-templatejson-properties) for customizing the Template. See [option resolution example](https://containers.dev/implementors/templates#option-resolution-example) for details. - -## Distributing Templates - -**Note**: *Allow GitHub Actions to create and approve pull requests* should be enabled in the repository's `Settings > Actions > General > Workflow permissions` for auto generation of `src/