Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,36 @@ As the remote can be configured differently (e.g. by including or excluding sub-

See [override-all.env](./override-all.env) for a file with all possible override variables.

The precedence for the overrides is:

1. Value set via feature parameter
2. Value set via environment variable
3. Values from `DEV_FEATURE_OVERRIDE_LOCATION`

#### Special overrides

There are a few sources which are used in multiple installations. For those sources, there is an override that globaly overrides all installations from this sources. Here is the list of those sources and their keys.

```
DEV_FEATURE_OVERRIDE_GITHUB_DOWNLOAD_URL=...
```
#### Unset an Override via Parameter

If an override is set, setting the corresponding parameter to `""` will not unset the override. To achieve this, set the parameter to `none`.

**Example:**
This environment variable is set: `DOCKER_OUT_CONFIG_PATH=https://example.com/config.json`

Then set this in your feature to explicitly unset it:

```json
{
"ghcr.io/postfinance/devcontainer-features/docker-out:0.3.0": {
"version": "28.3.3",
"configPath": "none"
}
}
```

### Extend an existing feature

Expand Down
4 changes: 3 additions & 1 deletion features/src/docker-out/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ Installs a Docker client which re-uses the host Docker socket.

```json
"features": {
"ghcr.io/postfinance/devcontainer-features/docker-out:0.2.0": {
"ghcr.io/postfinance/devcontainer-features/docker-out:0.3.0": {
"version": "latest",
"composeVersion": "latest",
"buildxVersion": "latest",
"configPath": "",
"downloadUrl": "",
"versionsUrl": "",
"composeDownloadUrl": "",
Expand All @@ -25,6 +26,7 @@ Installs a Docker client which re-uses the host Docker socket.
| version | The version of the Docker CLI to install. | string | latest | latest, 28.3.3, 20.10 |
| composeVersion | The version of the Compose plugin to install. | string | latest | latest, none, 2.39.1, 2.29 |
| buildxVersion | The version of the buildx plugin to install. | string | latest | latest, none, 0.26.1, 0.10 |
| configPath | Path or URL to a custom Docker client config.json file to copy into the container. | string | <empty> | /home/user/.docker/config.json, https://raw.githubusercontent.com/devcontainers/features/main/src/docker-out/config.json, none |
| downloadUrl | The download URL to use for Docker binaries. | string | <empty> | |
| versionsUrl | The URL to use for checking available versions. | string | <empty> | |
| composeDownloadUrl | The download URL to use for Docker Compose binaries. | string | <empty> | |
Expand Down
12 changes: 11 additions & 1 deletion features/src/docker-out/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "docker-out",
"version": "0.2.0",
"version": "0.3.0",
"name": "Docker outside Docker",
"description": "Installs a Docker client which re-uses the host Docker socket.",
"options": {
Expand Down Expand Up @@ -36,6 +36,16 @@
"default": "latest",
"description": "The version of the buildx plugin to install."
},
"configPath": {
"type": "string",
"default": "",
"description": "Path or URL to a custom Docker client config.json file to copy into the container.",
"proposals": [
"/home/user/.docker/config.json",
"https://raw.githubusercontent.com/devcontainers/features/main/src/docker-out/config.json",
"none"
]
},
"downloadUrl": {
"type": "string",
"default": "",
Expand Down
1 change: 1 addition & 0 deletions features/src/docker-out/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
-version="${VERSION:-"latest"}" \
-composeVersion="${COMPOSEVERSION:-"latest"}" \
-buildxVersion="${BUILDXVERSION:-"latest"}" \
-configPath="${CONFIGPATH:-""}" \
-downloadUrl="${DOWNLOADURL:-""}" \
-versionsUrl="${VERSIONSURL:-""}" \
-composeDownloadUrl="${COMPOSEDOWNLOADURL:-""}" \
Expand Down
43 changes: 42 additions & 1 deletion features/src/docker-out/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"flag"
"fmt"
"os"
"os/user"
"path/filepath"
"regexp"
"strconv"

"github.com/roemer/gotaskr/execr"
"github.com/roemer/gover"
Expand Down Expand Up @@ -36,10 +38,11 @@ func runMain() error {
version := flag.String("version", "latest", "")
composeVersion := flag.String("composeVersion", "latest", "")
buildxVersion := flag.String("buildxVersion", "latest", "")
buildxDownloadUrl := flag.String("buildxDownloadUrl", "", "")
downloadUrl := flag.String("downloadUrl", "", "")
versionsUrl := flag.String("versionsUrl", "", "")
composeDownloadUrl := flag.String("composeDownloadUrl", "", "")
buildxDownloadUrl := flag.String("buildxDownloadUrl", "", "")
configPath := flag.String("configPath", "", "")
flag.Parse()

// Load settings from an external file
Expand All @@ -51,13 +54,15 @@ func runMain() error {
installer.HandleOverride(versionsUrl, "https://download.docker.com/linux/static/stable", "docker-out-versions-url")
installer.HandleGitHubOverride(composeDownloadUrl, "docker/compose", "docker-out-compose-download-url")
installer.HandleGitHubOverride(buildxDownloadUrl, "docker/buildx", "docker-out-buildx-download-url")
installer.HandleOverride(configPath, "", "docker-out-config-path")

// Create and process the feature
feature := installer.NewFeature("Docker-Out", false,
&dockerCliComponent{
ComponentBase: installer.NewComponentBase("Docker CLI", *version),
DownloadUrl: *downloadUrl,
VersionsUrl: *versionsUrl,
ConfigPath: *configPath,
},
&dockerComposeComponent{
ComponentBase: installer.NewComponentBase("Docker Compose", *composeVersion),
Expand All @@ -81,6 +86,7 @@ type dockerCliComponent struct {
*installer.ComponentBase
DownloadUrl string
VersionsUrl string
ConfigPath string
}

func (c *dockerCliComponent) GetAllVersions() ([]*gover.Version, error) {
Expand Down Expand Up @@ -136,6 +142,41 @@ func (c *dockerCliComponent) InstallVersion(version *gover.Version) error {
if err := execr.Run(true, "cp", "-prf", "docker-init.sh", "/usr/local/share/docker-init.sh"); err != nil {
return err
}
// Copy the default config.json
if c.ConfigPath != "" {
fileContent, err := installer.ReadFileFromUrlOrLocal(c.ConfigPath)
if err != nil {
return err
}
userName := os.Getenv("_REMOTE_USER")
homeDir := os.Getenv("_REMOTE_USER_HOME")
if homeDir == "" {
homeDir = filepath.Join("/home", userName)
}
dockerDir := filepath.Join(homeDir, ".docker")
configDest := filepath.Join(dockerDir, "config.json")
// Ensure directory exists
if err := os.MkdirAll(dockerDir, 0700); err != nil {
return err
}
// Write config file
if err := os.WriteFile(configDest, fileContent, 0600); err != nil {
return err
}
// Set ownership
usr, err := user.Lookup(userName)
if err != nil {
return err
}
uid, _ := strconv.Atoi(usr.Uid)
gid, _ := strconv.Atoi(usr.Gid)
if err := os.Chown(dockerDir, uid, gid); err != nil {
return err
}
if err := os.Chown(configDest, uid, gid); err != nil {
return err
}
}

return nil
}
Expand Down
9 changes: 9 additions & 0 deletions features/test/docker-out/install-config.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash
set -e

[[ -f "$(dirname "$0")/../functions.sh" ]] && source "$(dirname "$0")/../functions.sh"
[[ -f "$(dirname "$0")/functions.sh" ]] && source "$(dirname "$0")/functions.sh"

check_version "$(docker version -f '{{.Client.Version}}')" "28.3.3"
check_file_exists "/home/vscode/.docker/config.json"
cat /home/vscode/.docker/config.json | grep "######" >/dev/null 2>&1 || (echo "Custom Docker config.json has a wrong content!" && exit 1)
16 changes: 16 additions & 0 deletions features/test/docker-out/scenarios.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,21 @@
"buildxVersion": "0.21.2"
}
}
},
"install-config": {
"build": {
"dockerfile": "Dockerfile",
"options": [
"--add-host=host.docker.internal:host-gateway"
]
},
"features": {
"./docker-out": {
"version": "28.3.3",
"composeVersion": "none",
"buildxVersion": "none",
"configPath": "https://raw.githubusercontent.com/postfinance/devcontainer-features/refs/heads/main/override-all.env"
}
}
}
}
41 changes: 26 additions & 15 deletions installer/overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,10 @@ import (

// Loads the feature overrides from a specified location.
func LoadOverrides() error {
var fileContent []byte
var err error
if overrideLocation := os.Getenv("DEV_FEATURE_OVERRIDE_LOCATION"); overrideLocation != "" {
// Load the overrides from the specified location
if strings.HasPrefix(overrideLocation, "http://") || strings.HasPrefix(overrideLocation, "https://") {
// Load from URL
fileContent, err = Tools.Download.AsBytes(overrideLocation)
if err != nil {
return fmt.Errorf("error downloading override file: %v", err)
}
} else {
// Load from file
fileContent, err = os.ReadFile(overrideLocation)
if err != nil {
return fmt.Errorf("error reading override file: %v", err)
}
fileContent, err := ReadFileFromUrlOrLocal(overrideLocation)
if err != nil {
return err
}
if len(fileContent) > 0 {
lines := strings.SplitSeq(strings.ReplaceAll(strings.TrimSpace(string(fileContent)), "\r\n", "\n"), "\n")
Expand All @@ -49,6 +37,23 @@ func LoadOverrides() error {
return nil
}

// ReadFileFromUrlOrLocal loads a file from a URL or local path and returns its contents as bytes.
func ReadFileFromUrlOrLocal(location string) ([]byte, error) {
if strings.HasPrefix(location, "http://") || strings.HasPrefix(location, "https://") {
fileContent, err := Tools.Download.AsBytes(location)
if err != nil {
return nil, fmt.Errorf("error downloading file: %v", err)
}
return fileContent, nil
} else {
fileContent, err := os.ReadFile(location)
if err != nil {
return nil, fmt.Errorf("error reading file: %v", err)
}
return fileContent, nil
}
}

func HandleOverride(passedValue *string, defaultValue string, key string) {
if *passedValue == "" {
// Convert the key to a compatible format
Expand All @@ -61,6 +66,12 @@ func HandleOverride(passedValue *string, defaultValue string, key string) {
// Otherwise set to default value
*passedValue = defaultValue
}
// Handle "none" value to explicitly unset the value if an override env variable is set to a different value
// e.g. --configPath="none" and DOCKER_OUT_CONFIG_PATH=https://example.com/config.json
// If we do not explicitly handle this, you could not unset a value once an override env variable is set
if strings.ToLower(*passedValue) == "none" {
*passedValue = ""
}
}

func HandleGitHubOverride(downloadUrl *string, gitHubPath string, key string) {
Expand Down
1 change: 1 addition & 0 deletions override-all.env
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ DOCKER_OUT_DOWNLOAD_URL=""
DOCKER_OUT_VERSIONS_URL=""
DOCKER_OUT_COMPOSE_DOWNLOAD_URL=""
DOCKER_OUT_BUILDX_DOWNLOAD_URL=""
DOCKER_OUT_CONFIG_PATH=""

# git-lfs
GIT_LFS_DOWNLOAD_URL=""
Expand Down