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 @@ -33,6 +33,7 @@ jobs:
"node",
"playwright-deps",
"sonar-scanner-cli",
"system-packages",
"timezone",
"vault-cli",
"zig"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Below is a list with included features, click on the link for more details.
| [node](./features/src/node/README.md) | A package which installs Node.js. |
| [playwright-deps](./features/src/playwright-deps/README.md) | A package which installs the needed dependencies to run Playwright. |
| [sonar-scanner-cli](./features/src/sonar-scanner-cli/README.md) | A package which installs the SonarScanner CLI. |
| [system-packages](./features/src/system-packages/README.md) | Install arbitrary system packages using apt or apk. |
| [timezone](./features/src/timezone/README.md) | A package which allows setting the timezone. |
| [vault-cli](./features/src/vault-cli/README.md) | A feature which installs the Vault CLI. |
| [zig](./features/src/zig/README.md) | A feature which installs Zig. |
Expand Down
11 changes: 11 additions & 0 deletions build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,17 @@ func init() {
return publishFeature("sonar-scanner-cli")
})

////////// system-packages
gotaskr.Task("Feature:system-packages:Package", func() error {
return packageFeature("system-packages")
})
gotaskr.Task("Feature:system-packages:Test", func() error {
return testFeature("system-packages")
})
gotaskr.Task("Feature:system-packages:Publish", func() error {
return publishFeature("system-packages")
})

////////// timezone
gotaskr.Task("Feature:timezone:Package", func() error {
return packageFeature("timezone")
Expand Down
5 changes: 5 additions & 0 deletions features/src/system-packages/NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Notes

### System Compatibility

Debian, Ubuntu, Alpine
25 changes: 25 additions & 0 deletions features/src/system-packages/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# System Packages (system-packages)

Install arbitrary system packages using apt or apk.

## Example Usage

```json
"features": {
"ghcr.io/postfinance/devcontainer-features/system-packages:0.1.0": {
"packages": ""
}
}
```

## Options

| Option | Description | Type | Default Value | Proposals |
|-----|-----|-----|-----|-----|
| packages | Comma-separated list of system packages to install. | string | <empty> | curl,git,htop, sshpass,yamllint,yq,gettext-base |

## Notes

### System Compatibility

Debian, Ubuntu, Alpine
17 changes: 17 additions & 0 deletions features/src/system-packages/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "system-packages",
"version": "0.1.0",
"name": "System Packages",
"description": "Install arbitrary system packages using apt or apk.",
"options": {
"packages": {
"type": "string",
"description": "Comma-separated list of system packages to install.",
"proposals": [
"curl,git,htop",
"sshpass,yamllint,yq,gettext-base"
],
"default": ""
}
}
}
4 changes: 4 additions & 0 deletions features/src/system-packages/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
. ./functions.sh

"./installer_$(detect_arch)" \
-packages="${PACKAGES:-""}"
60 changes: 60 additions & 0 deletions features/src/system-packages/installer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

import (
"builder/installer"
"flag"
"fmt"
"os"
"strings"

"github.com/roemer/gover"
)

func main() {
if err := runMain(); err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
}

func runMain() error {
packagesFlag := flag.String("packages", "", "Comma-separated list of system packages to install.")
flag.Parse()

packages := parsePackages(*packagesFlag)
if len(packages) == 0 {
fmt.Println("No packages specified for installation.")
return nil
}

feature := installer.NewFeature("System Packages", true,
&systemPackagesComponent{
ComponentBase: installer.NewComponentBase("System Packages", "system-default"),
Packages: packages,
})
return feature.Process()
}

type systemPackagesComponent struct {
*installer.ComponentBase
Packages []string
}

func (c *systemPackagesComponent) InstallVersion(version *gover.Version) error {
return installer.Tools.System.InstallPackages(c.Packages)
}

func parsePackages(flagValue string) []string {
if flagValue == "" {
return nil
}
parts := strings.Split(flagValue, ",")
var pkgs []string
for _, p := range parts {
trimmed := strings.TrimSpace(p)
if trimmed != "" {
pkgs = append(pkgs, trimmed)
}
}
return pkgs
}
9 changes: 9 additions & 0 deletions features/test/system-packages/ci-utility.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_command_exists "yq"
check_command_exists "yamllint"
check_command_exists "sshpass"
15 changes: 15 additions & 0 deletions features/test/system-packages/scenarios.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"ci-utility": {
"build": {
"dockerfile": "Dockerfile",
"options": [
"--add-host=host.docker.internal:host-gateway"
]
},
"features": {
"./system-packages": {
"packages": "yq,yamllint,sshpass"
}
}
}
}
6 changes: 6 additions & 0 deletions features/test/system-packages/test-images.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
"mcr.microsoft.com/devcontainers/base:debian-12",
"mcr.microsoft.com/devcontainers/base:debian-13",
"mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
"mcr.microsoft.com/devcontainers/base:alpine"
]
27 changes: 27 additions & 0 deletions installer/apk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package installer

import (
"strings"

"github.com/roemer/gotaskr/execr"
)

type apk struct{}

func (a apk) InstallDependencies(dependencies ...string) error {
args := append([]string{"add", "--no-cache"}, dependencies...)
if err := execr.Run(true, "apk", args...); err != nil {
return err
}
return nil
}

func (a apk) InstallLocalPackage(packagePath string) error {
if !strings.HasPrefix(packagePath, "./") {
packagePath = "./" + packagePath
}
if err := execr.Run(true, "apk", "add", "--no-cache", packagePath); err != nil {
return err
}
return nil
}
2 changes: 2 additions & 0 deletions installer/installer_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type tools struct {
System *system
Versioning *versioning
Apt *apt
Apk *apk
}

var Tools *tools
Expand All @@ -27,5 +28,6 @@ func init() {
System: &system{},
Versioning: &versioning{},
Apt: &apt{},
Apk: &apk{},
}
}
19 changes: 19 additions & 0 deletions installer/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ const (

type system struct{}

func (s *system) InstallPackages(packages []string) error {
osInfo, err := s.GetOsInfo()
if err != nil {
return err
}
switch {
case osInfo.IsDebian(), osInfo.IsUbuntu():
return Tools.Apt.InstallDependencies(packages...)
case osInfo.IsAlpine():
return Tools.Apk.InstallDependencies(packages...)
default:
return fmt.Errorf("unsupported OS vendor: %s", osInfo.Vendor)
}
}

func (s *system) MapArchitecture(mapping map[string]string) (string, error) {
mappedValue, ok := mapping[runtime.GOARCH]
if !ok {
Expand Down Expand Up @@ -58,6 +73,10 @@ func (v *OsInfo) IsUbuntu() bool {
return v.Vendor == "ubuntu"
}

func (v *OsInfo) IsAlpine() bool {
return v.Vendor == "alpine"
}

func (v *OsInfo) MajorVersion() int {
var major int
fmt.Sscanf(v.VersionId, "%d", &major)
Expand Down