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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,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
17 changes: 4 additions & 13 deletions features/src/browsers/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,6 @@ func runMain() error {
installer.HandleOverride(firefoxVersionsUrl, "https://product-details.mozilla.org/1.0/firefox.json", "firefox-versions-url")

// Fetch the os info
osInfo, err := installer.Tools.System.GetOsInfo()
if err != nil {
return fmt.Errorf("failed getting OS info: %w", err)
}

// Choose the correct chrome component
var chromeComponentToInstall installer.IComponent
Expand All @@ -71,14 +67,12 @@ func runMain() error {
ComponentBase: installer.NewComponentBase("Chrome Test", *chromeVersion),
TestingVersionsUrl: *chromeTestingVersionsUrl,
ChromeDownloadBaseUrl: *chromeDownloadUrl,
OsInfo: osInfo,
}
} else {
chromeComponentToInstall = &chromeComponent{
ComponentBase: installer.NewComponentBase("Chrome", *chromeVersion),
VersionsUrl: *chromeVersionsUrl,
ChromeDownloadBaseUrl: *chromeDownloadUrl,
OsInfo: osInfo,
}
}

Expand All @@ -90,7 +84,6 @@ func runMain() error {
VersionsUrl: *firefoxVersionsUrl,
FirefoxDownloadBaseUrl: *firefoxDownloadUrl,
ShouldResolveVersion: *firefoxVersionResolve,
OsInfo: osInfo,
})
return feature.Process()
}
Expand All @@ -103,7 +96,6 @@ type chromeComponent struct {
*installer.ComponentBase
VersionsUrl string
ChromeDownloadBaseUrl string
OsInfo *installer.OsInfo
}

func (c *chromeComponent) GetAllVersions() ([]*gover.Version, error) {
Expand Down Expand Up @@ -171,7 +163,6 @@ type chromeForTestingComponent struct {
*installer.ComponentBase
TestingVersionsUrl string
ChromeDownloadBaseUrl string
OsInfo *installer.OsInfo
}

func (c *chromeForTestingComponent) GetAllVersions() ([]*gover.Version, error) {
Expand Down Expand Up @@ -258,7 +249,7 @@ func (c *chromeForTestingComponent) InstallVersion(version *gover.Version) error
return err
}

libasoundPackageName := chooseLibasoundPackageName(c.OsInfo)
libasoundPackageName := chooseLibasoundPackageName()

// Install dependencies
// Taken from https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/dist_package_versions.json
Expand Down Expand Up @@ -319,7 +310,6 @@ type firefoxComponent struct {
VersionsUrl string
FirefoxDownloadBaseUrl string
ShouldResolveVersion bool
OsInfo *installer.OsInfo
}

func (c *firefoxComponent) GetAllVersions() ([]*gover.Version, error) {
Expand Down Expand Up @@ -370,7 +360,7 @@ func (c *firefoxComponent) InstallVersion(version *gover.Version) error {
return err
}

libasoundPackageName := chooseLibasoundPackageName(c.OsInfo)
libasoundPackageName := chooseLibasoundPackageName()

// Install dependencies
if err := installer.Tools.Apt.InstallDependencies(
Expand Down Expand Up @@ -399,7 +389,8 @@ func (c *firefoxComponent) IsFullVersion(referenceVersion *gover.Version) bool {
return c.ShouldResolveVersion
}

func chooseLibasoundPackageName(osInfo *installer.OsInfo) string {
func chooseLibasoundPackageName() string {
osInfo := installer.Tools.System.OsInfo()
if osInfo.IsDebian() && osInfo.MajorVersion() >= 13 {
return "libasound2t64"
}
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