Skip to content

Commit

Permalink
Add more examples of using the CLI (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
Chuxel authored Aug 8, 2022
1 parent b101324 commit fb94533
Show file tree
Hide file tree
Showing 15 changed files with 298 additions and 0 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ Hello, VS Code Remote - Containers!

Congrats, you've just run the dev container CLI and seen it in action!

## More CLI examples

The [example-usage](./example-usage) folder contains some simple shell scripts to illustrate how the CLI can be used to:

- Inject tools for use inside a development container
- Use a dev container as your CI build environment to build an application (even if it is not deployed as a container)
- Build a container image from a devcontainer.json file that includes [dev container features](https://containers.dev/implementors/features/#devcontainer-json-properties)

## Build from sources

This repository has a [dev container configuration](https://github.com/devcontainers/cli/tree/main/.devcontainer), which you can use to ensure you have the right dependencies installed.
Expand Down
94 changes: 94 additions & 0 deletions example-usage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Dev Container CLI Examples

This folder contains a set of basic examples that use the devcontainer CLI for different use cases. It includes example scripts to:

1. [Use three different tools from a development container](#tool-examples)
2. [Use a dev container as your CI build environment](#ci-build-environment-example) (even if your app is not deployed as a container)
3. [Build a container image](#building-an-image-from-devcontainerjson) from a devcontainer.json file that includes [dev container features](https://containers.dev/implementors/features/#devcontainer-json-properties)

Each should run on macOS or Linux. For Windows, you can use these scripts from WSL2.

## Pre-requisites

1. Install Node.js 16 (e.g., using [nvm](https://github.com/nvm-sh/nvm))
2. Install [node-gyp pre-requisites](https://github.com/nodejs/node-gyp):
- **Linux:** Use your distro's package manager. E.g. on Ubuntu/Debian: `sudo apt-get update && sudo apt-get install python3-minimal gcc g++ make`
- **macOS:** Install the XCode Command Line Tools ([more info](https://github.com/nodejs/node-gyp/blob/main/README.md#on-macos))
3. Make sure you have an OpenSSH compliant `ssh` command available and in your path if you plan to use the `Vim via SSH` example (it should already be there on macOS, and in Linux/WSL, you can install `openssh-client` using your distro's package manager if its missing)
3. Install the latest dev container CLI: `npm install -g @devcontainers/cli`

## Using the examples

All examples use the contents of the `workspace` folder for their configuration, which is where you can make modifications if you'd like. The example scripts are then in different sub-folders.

### Tool examples

You can use these examples by opening a terminal and typing one of the following:

- `tool-vscode-server/start.sh` - [VS Code Server](https://code.visualstudio.com/docs/remote/vscode-server) (official)
- `tool-openvscode-server/start.sh` - [openvscode-server](https://github.com/gitpod-io/openvscode-server)
- `tool-vim-via-ssh/start.sh` - Vim via an SSH connection. SSH is used primarily to demonstrate how this could be achieved from other SSH supporting client tools.

When switching between examples, pass `true` in as an argument to get the container recreated to avoid port conflicts. e.g., `./start.sh true`

In the first two examples, you'll be instructed to go to `http://localhost:8000` in a browser.

This also adds a desktop to the container that can be accessed from a web browser at `http://localhost:6080` and you can connect using the password `vscode`.

#### How the tool examples work

These examples demonstrate the use of the dev container CLI to:

1. Simplify setup using the "[dev container features](https://containers.dev/implementors/features/#devcontainer-json-properties)" concept. For example, SSH support is added just using a feature reference. See `workspace/.devcontainer/devcontainer.json` for more information.

2. How the dev container CLI can be used to inject tools without building them into the base image:

1. Use `devcontainer up` to spin up the container and mount a `server` and `workspace` folder into the container.
2. Use `devcontainer exec` to run a script from this mounted folder to set up the appropriate server.
3. In the `vim` example, a temporary SSH key is set up and configured, and then SSH is used from the command line to connect to the container once it is up and running. See `tool-vim-via-ssh/start.sh` for details.

Currently the `appPort` property is used in `devcontainer.json` instead of `forwardPorts` due to a gap in the current dev container CLI ([see here](https://github.com/devcontainers/cli/issues/22)).

### CI build environment example

This example illustrates how you can use the dev container CLI to build your application in any CI system. (Note there is also a [GitHub Action](https://github.com/marketplace/actions/devcontainers-ci) and [Azure DevOps task](https://marketplace.visualstudio.com/items?itemName=devcontainers.ci) if you are using those automation systems, but this example will focus on direct use of the CLI.)

You can use the example by opening a terminal and typing the following:

```
ci-app-build-script/build-app.sh
```

After the build completes, you can find the built application in the `workspace/dist` folder.

The initial build can take a bit since it is building the dev container image, which is an example of where [pre-building an image](#building-an-image-from-devcontainerjson) helps.

#### How the CI example works

This example demonstrates the use of the dev container CLI to:

1. Simplify setup using the "[dev container features](https://containers.dev/implementors/features/#devcontainer-json-properties)" concept. For example, SSH support is added just using a feature reference. See `workspace/.devcontainer/devcontainer.json` for more information.

2. Execute an application build script inside a dev container as follows:

1. Use `devcontainer up` to spin up the container and mount the the `workspace` folder into the container.
2. Use `devcontainer exec` to run a build script from the mounted folder inside the development container.
3. Delete the container when the build is finished.

All environment variables are automatically available from `exec`, including those that are are set in the non-root user's `.bashrc` file. The dev container CLI also automatically adjusts to UID/GID differences for the user inside the container on Linux to ensure the workspace folder is writable.

### Building an image from devcontainer.json

You can use the example by opening a terminal and typing the following:

```
image-build/build-image.sh
```

The resulting image name defaults to `devcontainer-cli-test-image`, but you can change it with the first argument, and configure it to push to a registry by setting the second argument to true. The third argument allows you to build for multiple architectures.

```
image-build/build-image.sh ghcr.io/my-org/my-image-name-here true "linux/amd64 linux/arm64"
```

Ultimately, this script just calls the `devcontainer build` command to do all the work. Once built, you can refer to the specified image name directly in a devcontainer.json file using the `image` property.
20 changes: 20 additions & 0 deletions example-usage/ci-app-build-script/build-application.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/sh
set -e
cd "$(dirname $0)"


build_date="$(date +%s)"

# Create a label for use during cleanup since the devcontainer CLI does
# not have a "remove" or "down" command yet (though this is planned).
id_label="ci-container=${build_date}"

# Run build
devcontainer up --id-label ${id_label} --workspace-folder ../workspace
set +e
devcontainer exec --id-label ${id_label} --workspace-folder ../workspace scripts/execute-app-build.sh
set -e

# Clean up.
echo "\nCleaning up..."
docker rm -f $(docker ps -aq --filter label=${id_label})
15 changes: 15 additions & 0 deletions example-usage/image-build/build-image.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh
set -e
cd "$(dirname $0)"

image_name="${1:-"devcontainer-cli-test-image"}"

# Push will upload the image to a registry when done (if logged in via docker CLI)
push_flag="${2:-false}"

# If more than one plaftorm is specified, then push must be set.
platforms="${3:-linux/amd64}"

devcontainer build --image-name $image_name --platform "$platforms" --push $push_flag --workspace-folder ../workspace

echo "\nImage ${image_name} built successfully!"
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh
set -e
if [ ! -e "$HOME/.openvscodeserver" ]; then
echo "Downloading openvscode-server..."
curl -fsSL https://github.com/gitpod-io/openvscode-server/releases/download/openvscode-server-v1.69.2/openvscode-server-v1.69.2-linux-x64.tar.gz -o /tmp/openvscode-server.tar.gz
mkdir -p $HOME/.openvscodeserver
echo "Extracting..."
tar --strip 1 -xzf /tmp/openvscode-server.tar.gz -C $HOME/.openvscodeserver/
rm -f /tmp/openvscode-server.tar.gz
fi
if [ "$(ps -ef | grep '\.openvscode-server' | wc -l)" = "1" ]; then
$HOME/.openvscodeserver/bin/openvscode-server serve-local --without-connection-token --host 0.0.0.0 --port 8000
else
echo "\ncode-server is already running.\n\nConnect using: http://localhost:8000\n"
fi
11 changes: 11 additions & 0 deletions example-usage/tool-openvscode-server/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh
set -e
cd "$(dirname $0)"

remove_flag=""
if [ "$1" = "true" ]; then
remove_flag="--remove-existing-container"
fi

devcontainer up $remove_flag --mount "type=bind,source=$(pwd)/server,target=/server" --workspace-folder ../workspace
devcontainer exec --workspace-folder . /server/init-openvscode-server.sh
13 changes: 13 additions & 0 deletions example-usage/tool-vim-via-ssh/server/init-vim.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh
set -e
if ! type vim > /dev/null 2>&1; then
echo "Installing vim..."
sudo apt-get update
sudo apt-get install -y vim
fi

# Copy generated keys
mkdir -p $HOME/.ssh
cat /server/temp-ssh-key.pub > $HOME/.ssh/authorized_keys
chmod 644 $HOME/.ssh/authorized_keys
chmod 700 $HOME/.ssh
23 changes: 23 additions & 0 deletions example-usage/tool-vim-via-ssh/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/sh
set -e
cd "$(dirname $0)"

remove_flag=""
if [ "$1" = "true" ]; then
remove_flag="--remove-existing-container"
fi

# Generate certificate
cd server
rm -f temp-ssh-key*
ssh-keygen -q -N '' -t rsa -f temp-ssh-key
cd ..

# Start container
devcontainer up $remove_flag --mount "type=bind,source=$(pwd)/server,target=/server" --workspace-folder ../workspace

# Install vim (if needed) and add pub key to SSH allow list
devcontainer exec --workspace-folder workspace /server/init-vim.sh

# Connect
ssh -t -i server/temp-ssh-key -o NoHostAuthenticationForLocalhost=yes -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null -p 2222 vscode@localhost exec bash -c vim
30 changes: 30 additions & 0 deletions example-usage/tool-vscode-server/server/init-vscode-server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/sh
set -e
INSTALL_LOCATION="$HOME/.local/bin"
INSTALL_TARGET=unknown-linux-gnu

if [ ! -e "$HOME/.vscode-cli" ]; then
# Adapted from https://aka.ms/install-vscode-server/setup.sh
install_arch=x86_64
arch=$(uname -m)
if [ $arch = "aarch64" ] || [ $arch = "arm64" ]; then
install_arch=aarch64
fi
install_url=https://aka.ms/vscode-server-launcher/$install_arch-$INSTALL_TARGET
echo "Installing from $install_url"
mkdir -p $INSTALL_LOCATION
if type curl > /dev/null 2>&1; then
curl -sSLf $install_url -o $INSTALL_LOCATION/code-server
elif type wget > /dev/null 2>&1; then
wget -q $install_url -O $INSTALL_LOCATION/code-server
else
echo "Installation failed. Please install curl or wget in your container image."
exit 1
fi
chmod +x $INSTALL_LOCATION/code-server
fi
if ! pidof code-server > /dev/null 2>&1; then
$INSTALL_LOCATION/code-server serve-local --without-connection-token --accept-server-license-terms --host 0.0.0.0 --port 8000
else
echo "\ncode-server is already running.\n\nConnect using: http://localhost:8000\n"
fi
11 changes: 11 additions & 0 deletions example-usage/tool-vscode-server/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh
set -e
cd "$(dirname $0)"

remove_flag=""
if [ "$1" = "true" ]; then
remove_flag="--remove-existing-container"
fi

devcontainer up $remove_flag --mount "type=bind,source=$(pwd)/server,target=/server" --workspace-folder ../workspace
devcontainer exec --workspace-folder . /server/init-vscode-server.sh
1 change: 1 addition & 0 deletions example-usage/workspace/.devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM mcr.microsoft.com/vscode/devcontainers/base:0-bullseye
36 changes: 36 additions & 0 deletions example-usage/workspace/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "devcontainer CLI Demo",
"build": {
"dockerfile": "Dockerfile"
},

// 👇 Dev Container Features - https://containers.dev/implementors/features/
"features": {
"ghcr.io/devcontainers/features/go:1": {
"version": "1.18.4"
},
"ghcr.io/devcontainers/features/node:1": {
"version": "16.15.1",
"nodeGypDependencies": false
},
"ghcr.io/devcontainers/features/desktop-lite:1": { },
"ghcr.io/devcontainers/features/docker-in-docker:1": { },
// Optional - For tools that require SSH
"ghcr.io/devcontainers/features/sshd:1": { }
},

// We are using appPort since forwardPorts not yet supported directly
// by the CLI. See https://github.com/devcontainers/cli/issues/22
// A pre-processor can easily parse devcontainer.json and inject
// these values as appropriate. We're omitting that for simplicity.
"appPort": [
// Expose SSH port for tools that need it (e.g. JetBrains)
"127.0.0.1:2222:2222",
// Port VS Code Server / openvscode-server is on
8000,
// Port for VNC web server contributed by the desktop-lite feature
6080
],

"remoteUser": "vscode"
}
3 changes: 3 additions & 0 deletions example-usage/workspace/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/devcontainers/cli/hello-world

go 1.18
7 changes: 7 additions & 0 deletions example-usage/workspace/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "fmt"

func main() {
fmt.Println("Hello world!")
}
11 changes: 11 additions & 0 deletions example-usage/workspace/scripts/execute-app-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh
set -e
cd "$(dirname $0)/.."

mkdir -p dist
echo "\nStarting build..."
GOARCH="amd64" GOOS="linux" go build -o ./dist/hello-world-linux ./main.go
GOARCH="amd64" GOOS="darwin" go build -o ./dist/hello-world-darwin ./main.go
GOARCH="amd64" GOOS="windows" go build -o ./dist/hello-world-windows.exe ./main.go

echo "\nApplication build complete! Check out the result in the workspace/dist folder."

0 comments on commit fb94533

Please sign in to comment.