diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..2fec915 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,16 @@ +// More info: https://containers.dev/implementors/json_reference/ +{ + "image": "mcr.microsoft.com/devcontainers/javascript-node:1-18-bullseye", + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {} + }, + "customizations": { + "vscode": { + "extensions": [ + "mads-hartmann.bash-ide-vscode", + "dbaeumer.vscode-eslint" + ] + } + }, + "postCreateCommand": "npm install -g @devcontainers/cli" +} diff --git a/.github/actions/smoke-test/action.yaml b/.github/actions/smoke-test/action.yaml new file mode 100644 index 0000000..5f98ba8 --- /dev/null +++ b/.github/actions/smoke-test/action.yaml @@ -0,0 +1,22 @@ +name: 'Smoke test' +inputs: + template: + description: 'Template to test' + required: true + +runs: + using: composite + steps: + - name: Checkout main + id: checkout_release + uses: actions/checkout@v3 + + - 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 new file mode 100755 index 0000000..a5b90eb --- /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 "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 test/test-utils/test-utils.sh ${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..36c2fb3 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,47 @@ +name: "Release Dev Container Templates & Generate Documentation" +on: + workflow_dispatch: + +jobs: + deploy: + if: ${{ github.ref == 'refs/heads/main' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: "Publish Templates" + uses: devcontainers/action@v1 + with: + publish-templates: "true" + base-path-to-templates: "./src" + generate-docs: "true" + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create PR for Documentation + id: push_image_info + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + echo "Start." + + # Configure git and Push updates + git config --global user.email github-actions[bot]@users.noreply.github.com + git config --global user.name github-actions[bot] + git config pull.rebase false + + branch=automated-documentation-update-$GITHUB_RUN_ID + git checkout -b $branch + message='Automated documentation update' + + # Add / update and commit + git add */**/README.md + git commit -m 'Automated documentation update [skip ci]' || export NO_UPDATES=true + + # Push + if [ "$NO_UPDATES" != "true" ] ; then + git push origin "$branch" + gh pr create --title "$message" --body "$message" + fi diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml new file mode 100644 index 0000000..778bed4 --- /dev/null +++ b/.github/workflows/test-pr.yaml @@ -0,0 +1,32 @@ +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@v2 + 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@v3 + + - name: Smoke test for '${{ matrix.templates }}' + id: smoke_test + uses: ./.github/actions/smoke-test + with: + template: "${{ matrix.templates }}" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..35bef52 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..974ac6d --- /dev/null +++ b/README.md @@ -0,0 +1,122 @@ +# Dev Container Templates: Self Authoring Guide + +> 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). + +## Repo and Template Structure + +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`. + +``` +├── 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 +... +``` + +### 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/