diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..ad4c73b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,25 @@ +{ + "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bookworm", + "customizations": { + "vscode": { + "settings": { + "json.schemas": [ + { + "fileMatch": [ + "*/devcontainer-feature.json" + ], + "url": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainerFeature.schema.json" + } + ] + }, + "extensions": [ + "mads-hartmann.bash-ide-vscode" + ] + } + }, + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {} + }, + "remoteUser": "node", + "updateContentCommand": "npm install -g @devcontainers/cli" +} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..9ba1e62 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,47 @@ +name: "Release dev container features & Generate Documentation" +on: + workflow_dispatch: + +jobs: + deploy: + if: ${{ github.ref == 'refs/heads/main' }} + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + packages: write + steps: + - uses: actions/checkout@v3 + + - name: "Publish Features" + uses: devcontainers/action@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: + 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.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..9a32de6 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,58 @@ +name: "CI - Test Features" +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + test-autogenerated: + runs-on: ubuntu-latest + continue-on-error: true + strategy: + matrix: + features: + - color + - hello + baseImage: + - debian:latest + - ubuntu:latest + - mcr.microsoft.com/devcontainers/base:ubuntu + steps: + - uses: actions/checkout@v3 + + - name: "Install latest devcontainer CLI" + run: npm install -g @devcontainers/cli + + - name: "Generating tests for '${{ matrix.features }}' against '${{ matrix.baseImage }}'" + run: devcontainer features test --skip-scenarios -f ${{ matrix.features }} -i ${{ matrix.baseImage }} . + + test-scenarios: + runs-on: ubuntu-latest + continue-on-error: true + strategy: + matrix: + features: + - color + - hello + steps: + - uses: actions/checkout@v3 + + - name: "Install latest devcontainer CLI" + run: npm install -g @devcontainers/cli + + - name: "Generating tests for '${{ matrix.features }}' scenarios" + run: devcontainer features test -f ${{ matrix.features }} --skip-autogenerated --skip-duplicated . + + test-global: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v3 + + - name: "Install latest devcontainer CLI" + run: npm install -g @devcontainers/cli + + - name: "Testing global scenarios" + run: devcontainer features test --global-scenarios-only . diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..5dcc21b --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,16 @@ +name: "Validate devcontainer-feature.json files" +on: + workflow_dispatch: + pull_request: + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: "Validate devcontainer-feature.json files" + uses: devcontainers/action@v1 + with: + validate-only: "true" + base-path-to-features: "./src" 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..1a1cea8 --- /dev/null +++ b/README.md @@ -0,0 +1,188 @@ +# Dev Container Features: Self Authoring Template + +> This repo provides a starting point and example for creating your own custom [dev container Features](https://containers.dev/implementors/features/), hosted for free on GitHub Container Registry. The example in this repository follows the [dev container Feature distribution specification](https://containers.dev/implementors/features-distribution/). +> +> To provide feedback to the specification, please leave a comment [on spec issue #70](https://github.com/devcontainers/spec/issues/70). For more broad feedback regarding dev container Features, please see [spec issue #61](https://github.com/devcontainers/spec/issues/61). + +## Example Contents + +This repository contains a _collection_ of two Features - `hello` and `color`. These Features serve as simple feature implementations. Each sub-section below shows a sample `devcontainer.json` alongside example usage of the Feature. + +### `hello` + +Running `hello` inside the built container will print the greeting provided to it via its `greeting` option. + +```jsonc +{ + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "ghcr.io/devcontainers/feature-starter/hello:1": { + "greeting": "Hello" + } + } +} +``` + +```bash +$ hello + +Hello, user. +``` + +### `color` + +Running `color` inside the built container will print your favorite color to standard out. + +```jsonc +{ + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "ghcr.io/devcontainers/feature-starter/color:1": { + "favorite": "green" + } + } +} +``` + +```bash +$ color + +my favorite color is green +``` + +## Repo and Feature Structure + +Similar to the [`devcontainers/features`](https://github.com/devcontainers/features) repo, this repository has a `src` folder. Each Feature has its own sub-folder, containing at least a `devcontainer-feature.json` and an entrypoint script `install.sh`. + +``` +├── src +│ ├── hello +│ │ ├── devcontainer-feature.json +│ │ └── install.sh +│ ├── color +│ │ ├── devcontainer-feature.json +│ │ └── install.sh +| ├── ... +│ │ ├── devcontainer-feature.json +│ │ └── install.sh +... +``` + +An [implementing tool](https://containers.dev/supporting#tools) will composite [the documented dev container properties](https://containers.dev/implementors/features/#devcontainer-feature-json-properties) from the feature's `devcontainer-feature.json` file, and execute in the `install.sh` entrypoint script in the container during build time. Implementing tools are also free to process attributes under the `customizations` property as desired. + +### Options + +All available options for a Feature should be declared in the `devcontainer-feature.json`. The syntax for the `options` property can be found in the [devcontainer Feature json properties reference](https://containers.dev/implementors/features/#devcontainer-feature-json-properties). + +For example, the `color` feature provides an enum of three possible options (`red`, `gold`, `green`). If no option is provided in a user's `devcontainer.json`, the value is set to "red". + +```jsonc +{ + // ... + "options": { + "favorite": { + "type": "string", + "enum": [ + "red", + "gold", + "green" + ], + "default": "red", + "description": "Choose your favorite color." + } + } +} +``` + +Options are exported as Feature-scoped environment variables. The option name is captialized and sanitized according to [option resolution](https://containers.dev/implementors/features/#option-resolution). + +```bash +#!/bin/bash + +echo "Activating feature 'color'" +echo "The provided favorite color is: ${FAVORITE}" + +... +``` + +## Distributing Features + +### Versioning + +Features are individually versioned by the `version` attribute in a Feature's `devcontainer-feature.json`. Features are versioned according to the semver specification. More details can be found in [the dev container Feature specification](https://containers.dev/implementors/features/#versioning). + +### Publishing + +> NOTE: The Distribution spec can be [found here](https://containers.dev/implementors/features-distribution/). +> +> While any registry [implementing the OCI Distribution spec](https://github.com/opencontainers/distribution-spec) can be used, this template will leverage GHCR (GitHub Container Registry) as the backing registry. + +Features are meant to be easily sharable units of dev container configuration and installation code. + +This repo contains a **GitHub Action** [workflow](.github/workflows/release.yaml) that will publish each Feature to GHCR. + +*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//README.md` per Feature (which merges any existing `src//NOTES.md`). + +By default, each Feature will be prefixed with the `` namespace. For example, the two Features in this repository can be referenced in a `devcontainer.json` with: + +``` +ghcr.io/devcontainers/feature-starter/color:1 +ghcr.io/devcontainers/feature-starter/hello:1 +``` + +The provided GitHub Action will also publish a third "metadata" package with just the namespace, eg: `ghcr.io/devcontainers/feature-starter`. This contains information useful for tools aiding in Feature discovery. + +'`devcontainers/feature-starter`' is known as the feature collection namespace. + +### Marking Feature Public + +Note that by default, GHCR packages are marked as `private`. To stay within the free tier, Features need to be marked as `public`. + +This can be done by navigating to the Feature's "package settings" page in GHCR, and setting the visibility to 'public`. The URL may look something like: + +``` +https://github.com/users//packages/container/%2F/settings +``` + +image + +### Adding Features to the Index + +If you'd like your Features to appear in our [public index](https://containers.dev/features) so that other community members can find them, you can do the following: + +* Go to [github.com/devcontainers/devcontainers.github.io](https://github.com/devcontainers/devcontainers.github.io) + * This is the GitHub repo backing the [containers.dev](https://containers.dev/) spec site +* Open a PR to modify the [collection-index.yml](https://github.com/devcontainers/devcontainers.github.io/blob/gh-pages/_data/collection-index.yml) file + +This index is from where [supporting tools](https://containers.dev/supporting) like [VS Code Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) and [GitHub Codespaces](https://github.com/features/codespaces) surface Features for their dev container creation UI. + +#### Using private Features in Codespaces + +For any Features hosted in GHCR that are kept private, the `GITHUB_TOKEN` access token in your environment will need to have `package:read` and `contents:read` for the associated repository. + +Many implementing tools use a broadly scoped access token and will work automatically. GitHub Codespaces uses repo-scoped tokens, and therefore you'll need to add the permissions in `devcontainer.json` + +An example `devcontainer.json` can be found below. + +```jsonc +{ + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "ghcr.io/my-org/private-features/hello:1": { + "greeting": "Hello" + } + }, + "customizations": { + "codespaces": { + "repositories": { + "my-org/private-features": { + "permissions": { + "packages": "read", + "contents": "read" + } + } + } + } + } +} +``` diff --git a/src/color/README.md b/src/color/README.md new file mode 100644 index 0000000..a1e7d1b --- /dev/null +++ b/src/color/README.md @@ -0,0 +1,26 @@ + +# My Favorite Color (color) + +A feature to remind you of your favorite color + +## Example Usage + +```json +"features": { + "ghcr.io/devcontainers/feature-starter/color:1": { + "version": "latest" + } +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| favorite | Choose your favorite color. | string | red | + + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/feature-starter/blob/main/src/color/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/color/devcontainer-feature.json b/src/color/devcontainer-feature.json new file mode 100644 index 0000000..e14e7e7 --- /dev/null +++ b/src/color/devcontainer-feature.json @@ -0,0 +1,21 @@ +{ + "name": "My Favorite Color", + "id": "color", + "version": "1.0.3", + "description": "A feature to remind you of your favorite color", + "options": { + "favorite": { + "type": "string", + "enum": [ + "red", + "gold", + "green" + ], + "default": "red", + "description": "Choose your favorite color." + } + }, + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ] +} diff --git a/src/color/install.sh b/src/color/install.sh new file mode 100644 index 0000000..883c25d --- /dev/null +++ b/src/color/install.sh @@ -0,0 +1,26 @@ +#!/bin/sh +set -e + +echo "Activating feature 'color'" +echo "The provided favorite color is: ${FAVORITE}" + + +# The 'install.sh' entrypoint script is always executed as the root user. +# +# These following environment variables are passed in by the dev container CLI. +# These may be useful in instances where the context of the final +# remoteUser or containerUser is useful. +# For more details, see https://containers.dev/implementors/features#user-env-var +echo "The effective dev container remoteUser is '$_REMOTE_USER'" +echo "The effective dev container remoteUser's home directory is '$_REMOTE_USER_HOME'" + +echo "The effective dev container containerUser is '$_CONTAINER_USER'" +echo "The effective dev container containerUser's home directory is '$_CONTAINER_USER_HOME'" + +cat > /usr/local/bin/color \ +<< EOF +#!/bin/sh +echo "my favorite color is ${FAVORITE}" +EOF + +chmod +x /usr/local/bin/color diff --git a/src/hello/README.md b/src/hello/README.md new file mode 100644 index 0000000..142e101 --- /dev/null +++ b/src/hello/README.md @@ -0,0 +1,26 @@ + +# Hello, World! (hello) + +A hello world feature + +## Example Usage + +```json +"features": { + "ghcr.io/devcontainers/feature-starter/hello:1": { + "version": "latest" + } +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| greeting | Select a pre-made greeting, or enter your own | string | hey | + + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/feature-starter/blob/main/src/hello/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/hello/devcontainer-feature.json b/src/hello/devcontainer-feature.json new file mode 100644 index 0000000..e2a3178 --- /dev/null +++ b/src/hello/devcontainer-feature.json @@ -0,0 +1,22 @@ +{ + "name": "Hello, World!", + "id": "hello", + "version": "1.0.2", + "description": "A hello world feature", + "options": { + "greeting": { + "type": "string", + "proposals": [ + "hey", + "hello", + "hi", + "howdy" + ], + "default": "hey", + "description": "Select a pre-made greeting, or enter your own" + } + }, + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ] +} diff --git a/src/hello/install.sh b/src/hello/install.sh new file mode 100644 index 0000000..480e262 --- /dev/null +++ b/src/hello/install.sh @@ -0,0 +1,29 @@ +#!/bin/sh +set -e + +echo "Activating feature 'hello'" + +GREETING=${GREETING:-undefined} +echo "The provided greeting is: $GREETING" + +# The 'install.sh' entrypoint script is always executed as the root user. +# +# These following environment variables are passed in by the dev container CLI. +# These may be useful in instances where the context of the final +# remoteUser or containerUser is useful. +# For more details, see https://containers.dev/implementors/features#user-env-var +echo "The effective dev container remoteUser is '$_REMOTE_USER'" +echo "The effective dev container remoteUser's home directory is '$_REMOTE_USER_HOME'" + +echo "The effective dev container containerUser is '$_CONTAINER_USER'" +echo "The effective dev container containerUser's home directory is '$_CONTAINER_USER_HOME'" + +cat > /usr/local/bin/hello \ +<< EOF +#!/bin/sh +RED='\033[0;91m' +NC='\033[0m' # No Color +echo "\${RED}${GREETING}, \$(whoami)!\${NC}" +EOF + +chmod +x /usr/local/bin/hello \ No newline at end of file diff --git a/test/_global/color_and_hello.sh b/test/_global/color_and_hello.sh new file mode 100644 index 0000000..9a08758 --- /dev/null +++ b/test/_global/color_and_hello.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# The 'test/_global' folder is a special test folder that is not tied to a single feature. +# +# This test file is executed against a running container constructed +# from the value of 'color_and_hello' in the tests/_global/scenarios.json file. +# +# The value of a scenarios element is any properties available in the 'devcontainer.json'. +# Scenarios are useful for testing specific options in a feature, or to test a combination of features. +# +# This test can be run with the following command (from the root of this repo) +# devcontainer features test --global-scenarios-only . + +set -e + +# Optional: Import test library bundled with the devcontainer CLI +source dev-container-features-test-lib + +echo -e "The result of the 'color' command will be:\n" +color +echo -e "The result of the 'hello' command will be:\n" +hello +echo -e "\n" + +# Feature-specific tests +# The 'check' command comes from the dev-container-features-test-lib. +check "check purple is my favorite color" bash -c "color | grep 'my favorite color is purple'" +check "check I am greeting with 'Greetings'" bash -c "hello | grep 'Greetings, $(whoami)'" + + +# Report result +# If any of the checks above exited with a non-zero exit code, the test will fail. +reportResults diff --git a/test/_global/scenarios.json b/test/_global/scenarios.json new file mode 100644 index 0000000..b046998 --- /dev/null +++ b/test/_global/scenarios.json @@ -0,0 +1,13 @@ +{ + "color_and_hello": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "color": { + "favorite": "purple" + }, + "hello": { + "greeting": "Greetings" + } + } + } +} \ No newline at end of file diff --git a/test/color/gold.sh b/test/color/gold.sh new file mode 100644 index 0000000..1aaa9aa --- /dev/null +++ b/test/color/gold.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# This test file will be executed against one of the scenarios devcontainer.json test that +# includes the 'color' feature with "favorite": "gold" option. + +set -e + +# Optional: Import test library bundled with the devcontainer CLI +source dev-container-features-test-lib + +# Feature-specific tests +# The 'check' command comes from the dev-container-features-test-lib. +check "execute command" bash -c "color | grep 'my favorite color is gold'" + +# Report result +# If any of the checks above exited with a non-zero exit code, the test will fail. +reportResults diff --git a/test/color/green.sh b/test/color/green.sh new file mode 100644 index 0000000..5b55388 --- /dev/null +++ b/test/color/green.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# This test file will be executed against one of the scenarios devcontainer.json test that +# includes the 'color' feature with "favorite": "green" option. + +set -e + +# Optional: Import test library bundled with the devcontainer CLI +source dev-container-features-test-lib + +# Feature-specific tests +# The 'check' command comes from the dev-container-features-test-lib. +check "execute command" bash -c "color | grep 'my favorite color is green'" + +# Report result +# If any of the checks above exited with a non-zero exit code, the test will fail. +reportResults diff --git a/test/color/my_favorite_color_is_green.sh b/test/color/my_favorite_color_is_green.sh new file mode 100644 index 0000000..94e6295 --- /dev/null +++ b/test/color/my_favorite_color_is_green.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# This test file will be executed against after building a container with the +# 'my_favorite_color_is_green' scenario in 'test/test/color/scenarios.json'. +# +# For more information, see: https://github.com/devcontainers/cli/blob/main/docs/features/test.md +# +# This scenario first uses the 'common-utils' Features to add a new user 'octocat'. +# It then installs the 'color' Feature with the FAVORITE option set to 'green' (the default FAVORITE value if none provided is 'red'). +# +# +# This test (as well as any of the other scenarios in 'scenarios.json') can be run with the following command: +# +# devcontainer features test \ +# --features color \ +# --skip-autogenerated \ +# /path/to/this/repo + +set -e + +# Optional: Import test library bundled with the devcontainer CLI +# Provides the 'check' and 'reportResults' commands. +source dev-container-features-test-lib + +# Feature-specific tests +# The 'check' command comes from the dev-container-features-test-lib. +check "validate favorite color" color | grep 'my favorite color is green' + +# Report result +# If any of the checks above exited with a non-zero exit code, the test will fail. +reportResults diff --git a/test/color/scenarios.json b/test/color/scenarios.json new file mode 100644 index 0000000..bfd7952 --- /dev/null +++ b/test/color/scenarios.json @@ -0,0 +1,33 @@ +{ + "my_favorite_color_is_green": { + "image": "mcr.microsoft.com/devcontainers/base:focal", + "features": { + "ghcr.io/devcontainers/features/common-utils:1": { + "installZsh": false, + "installOhMyZsh": false, + "upgradePackages": false, + "username": "octocat" + }, + "color": { + "favorite": "green" + } + }, + "remoteUser": "octocat" + }, + "gold": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "color": { + "favorite": "gold" + } + } + }, + "green": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "color": { + "favorite": "green" + } + } + } +} diff --git a/test/color/test.sh b/test/color/test.sh new file mode 100644 index 0000000..bab0fa3 --- /dev/null +++ b/test/color/test.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# This test file will be executed against an auto-generated devcontainer.json that +# includes the 'color' Feature with no options. +# +# For more information, see: https://github.com/devcontainers/cli/blob/main/docs/features/test.md +# +# Eg: +# { +# "image": "<..some-base-image...>", +# "features": { +# "color": {} +# }, +# "remoteUser": "root" +# } +# +# Thus, the value of all options will fall back to the default value in the +# Feature's 'devcontainer-feature.json'. +# For the 'color' feature, that means the default favorite color is 'red'. +# +# These scripts are run as 'root' by default. Although that can be changed +# with the '--remote-user' flag. +# +# This test can be run with the following command: +# +# devcontainer features test \ +# --features color \ +# --remote-user root \ +# --skip-scenarios \ +# --base-image mcr.microsoft.com/devcontainers/base:ubuntu \ +# /path/to/this/repo + +set -e + +# Optional: Import test library bundled with the devcontainer CLI +# See https://github.com/devcontainers/cli/blob/HEAD/docs/features/test.md#dev-container-features-test-lib +# Provides the 'check' and 'reportResults' commands. +source dev-container-features-test-lib + +# Feature-specific tests +# The 'check' command comes from the dev-container-features-test-lib. Syntax is... +# check