Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ jobs:
"build-essential",
"claude-code",
"cypress-deps",
"docker-in",
"docker-out",
"dotnet",
"eclipse-deps",
Expand Down
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{
"files.associations": {
"**/features/src/docker-in/dind-init.sh.tmpl": "shellscript",
"**/features/src/docker-out/docker-init.sh.tmpl": "shellscript"
},
"json.schemaDownload.trustedDomains": {
"https://docs.renovatebot.com/renovate-schema.json": true
}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Below is a list with included features, click on the link for more details.
| [build-essential](./features/src/build-essential/README.md) | Installs build essentials like gcc. |
| [claude-code](./features/src/claude-code/README.md) | Installs Claude Code, Anthropic's AI coding assistant CLI. |
| [cypress-deps](./features/src/cypress-deps/README.md) | Installs all dependencies required to run Cypress. |
| [docker-in](./features/src/docker-in/README.md) | Installs and runs a full Docker daemon. |
| [docker-out](./features/src/docker-out/README.md) | Installs a Docker client which re-uses the host Docker socket. |
| [dotnet](./features/src/dotnet/README.md) | A package which installs .NET SDKs, runtimes and workloads. |
| [eclipse-deps](./features/src/eclipse-deps/README.md) | Installs all dependencies required to run the Eclipse IDE. |
Expand Down
27 changes: 26 additions & 1 deletion build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ func init() {
gotaskr.Task("Feature:cypress-deps:Package", func() error { return packageFeature("cypress-deps") })
gotaskr.Task("Feature:cypress-deps:Test", func() error { return testFeature("cypress-deps") })

////////// docker-in
gotaskr.Task("Feature:docker-in:Package", func() error { return packageFeature("docker-in") })
gotaskr.Task("Feature:docker-in:Test", func() error { return testFeature("docker-in") })

////////// docker-out
gotaskr.Task("Feature:docker-out:Package", func() error { return packageFeature("docker-out") })
gotaskr.Task("Feature:docker-out:Test", func() error { return testFeature("docker-out") })
Expand Down Expand Up @@ -397,7 +401,14 @@ func testFeature(featureName string) error {
return err
}

// Read the feature json to see if some settings are relevant for build/run
featureSpec, err := ParseFeatureJson(copiedFeaturePath)
if err != nil {
return err
}

// Build the devcontainer
log.Information("Building the devcontainer for testing...")
imageName := fmt.Sprintf("dev-container-feature-%s-scenario-%s-test", featureName, scenarioName)
if err := gotaskr.Tools.DevContainerCli.Build(&gttools.DevContainerCliBuildSettings{
ToolSettingsBase: gttools.ToolSettingsBase{OutputToConsole: true},
Expand All @@ -411,7 +422,21 @@ func testFeature(featureName string) error {
}

// Run the check in the container
checkError := goext.CmdRunners.Console.Run("docker", "run", "-t", "--rm", "-v", "/var/run/docker.sock:/var/run/docker.sock", imageName, "sh", "-c", "/tmp/check.sh")
log.Information("Running the check in the container...")
runArgs := []string{"run", "-t", "--rm"}
if featureSpec.Privileged {
runArgs = append(runArgs, "--privileged")
}
if len(featureSpec.Mounts) > 0 {
for _, mount := range featureSpec.Mounts {
runArgs = append(runArgs, "-v", fmt.Sprintf("%s:%s", ExpandDevContainerFeatureVars(mount.Source), mount.Target))
}
}
if featureSpec.EntryPoint != "" {
runArgs = append(runArgs, "--entrypoint", featureSpec.EntryPoint)
}
runArgs = append(runArgs, imageName, "sh", "-c", "/tmp/check.sh")
checkError := goext.CmdRunners.Console.Run("docker", runArgs...)
if checkError != nil {
return fmt.Errorf("check failed: %w", checkError)
}
Expand Down
26 changes: 26 additions & 0 deletions build/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package main
import (
"bytes"
"encoding/json"
"math/rand"
"os"
"path/filepath"
"sort"
"strings"
"time"
)

// Reads and parses the "devcontainer-feature.json" file.
Expand All @@ -28,6 +31,15 @@ type FeatureSpec struct {
Description string `json:"description"`
Options OrderedOptionsMap `json:"options"`
Customizations FeatureCustomizations `json:"customizations"`
Privileged bool `json:"privileged"`
Mounts []FeatureMount `json:"mounts"`
EntryPoint string `json:"entrypoint"`
}

type FeatureMount struct {
Source string `json:"source"`
Target string `json:"target"`
Type string `json:"type"`
}

type FeatureOption struct {
Expand Down Expand Up @@ -64,3 +76,17 @@ func (om *OrderedOptionsMap) UnmarshalJSON(b []byte) error {
sort.Slice(om.Order, func(i, j int) bool { return index[om.Order[i]] < index[om.Order[j]] })
return nil
}

func ExpandDevContainerFeatureVars(value string) string {
randomId := randomStringWithCharset(16, "abcdefghijklmnopqrstuvwxyz0123456789")
return strings.ReplaceAll(value, "${devcontainerId}", randomId)
}

func randomStringWithCharset(length int, charset string) string {
var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))
b := make([]byte, length)
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
}
return string(b)
}
16 changes: 4 additions & 12 deletions features/src/apm/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"os"
"path/filepath"
"regexp"
"strconv"

"github.com/roemer/gover"
)
Expand All @@ -31,17 +30,10 @@ func main() {

func runMain() error {
// Check Preconditions
osInfo, err := installer.Tools.System.GetOsInfo()
if err != nil {
return fmt.Errorf("failed getting os info: %v", err)
}
if osInfo.Vendor == "debian" {
versionId, err := strconv.Atoi(osInfo.VersionId)
if err != nil {
return fmt.Errorf("failed parsing the version number from %s: %v", osInfo.Vendor, err)
}
if versionId < 13 {
return fmt.Errorf("unsupported debian version: %d", versionId)
osInfo := installer.Tools.System.OsInfo()
if osInfo.IsDebian() {
if osInfo.MajorVersion() < 13 {
return fmt.Errorf("unsupported debian version: %s", osInfo.VersionId)
}
}

Expand Down
6 changes: 1 addition & 5 deletions features/src/claude-code/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,8 @@ func (c *claudeCodeComponent) InstallVersion(version *gover.Version) error {
}

// Use musl variant for Alpine (musl libc)
osInfo, err := installer.Tools.System.GetOsInfo()
if err != nil {
return err
}
var fileName string
if osInfo.IsAlpine() {
if installer.Tools.System.OsInfo().IsAlpine() {
fileName = fmt.Sprintf("claude-linux-%s-musl.tar.gz", archPart)
} else {
fileName = fmt.Sprintf("claude-linux-%s.tar.gz", archPart)
Expand Down
5 changes: 1 addition & 4 deletions features/src/cypress-deps/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ func main() {

func runMain() error {
fmt.Println("Installing Cypress Dependencies")
osInfo, err := installer.Tools.System.GetOsInfo()
if err != nil {
return fmt.Errorf("failed to get OS info: %w", err)
}
osInfo := installer.Tools.System.OsInfo()

// Common dependencies for all supported distros
commonDeps := []string{
Expand Down
39 changes: 39 additions & 0 deletions features/src/docker-in/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Docker inside Docker (docker-in)

Installs and runs a full Docker daemon.

## Example Usage

```json
"features": {
"ghcr.io/postfinance/devcontainer-features/docker-in:1.0.0": {
"version": "latest",
"composeVersion": "latest",
"buildxVersion": "latest",
"configPath": "",
"downloadUrl": "",
"versionsUrl": "",
"composeDownloadUrl": "",
"buildxDownloadUrl": ""
}
}
```

## Options

| Option | Description | Type | Default Value | Proposals |
|-----|-----|-----|-----|-----|
| version | The version of the Docker Engine and 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 | &lt;empty&gt; | /home/user/.docker/config.json, https://raw.githubusercontent.com/devcontainers/features/main/src/docker-in/config.json, none |
| downloadUrl | The download URL to use for Docker binaries (engine, CLI, containerd). | string | &lt;empty&gt; | |
| versionsUrl | The URL to use for checking available versions. | string | &lt;empty&gt; | |
| composeDownloadUrl | The download URL to use for Docker Compose binaries. | string | &lt;empty&gt; | |
| buildxDownloadUrl | The download URL to use for Docker Buildx binaries. | string | &lt;empty&gt; | |

## Customizations

### VS Code Extensions

- `ms-azuretools.vscode-containers`
91 changes: 91 additions & 0 deletions features/src/docker-in/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"id": "docker-in",
"version": "1.0.0",
"name": "Docker inside Docker",
"description": "Installs and runs a full Docker daemon.",
"options": {
"version": {
"type": "string",
"proposals": [
"latest",
"28.3.3",
"20.10"
],
"default": "latest",
"description": "The version of the Docker Engine and CLI to install."
},
"composeVersion": {
"type": "string",
"proposals": [
"latest",
"none",
"2.39.1",
"2.29"
],
"default": "latest",
"description": "The version of the Compose plugin to install."
},
"buildxVersion": {
"type": "string",
"proposals": [
"latest",
"none",
"0.26.1",
"0.10"
],
"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-in/config.json",
"none"
]
},
"downloadUrl": {
"type": "string",
"default": "",
"description": "The download URL to use for Docker binaries (engine, CLI, containerd)."
},
"versionsUrl": {
"type": "string",
"default": "",
"description": "The URL to use for checking available versions."
},
"composeDownloadUrl": {
"type": "string",
"default": "",
"description": "The download URL to use for Docker Compose binaries."
},
"buildxDownloadUrl": {
"type": "string",
"default": "",
"description": "The download URL to use for Docker Buildx binaries."
}
},
"customizations": {
"vscode": {
"extensions": [
"ms-azuretools.vscode-containers"
]
}
},
"mounts": [
{
"source": "dind-var-lib-docker-${devcontainerId}",
"target": "/var/lib/docker",
"type": "volume"
},
{
"source": "dind-var-lib-containerd-${devcontainerId}",
"target": "/var/lib/containerd",
"type": "volume"
}
],
"entrypoint": "/usr/local/share/dind-init.sh",
"privileged": true
}
Loading