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 @@ -62,6 +62,7 @@ jobs:
fail-fast: ${{ github.event_name != 'schedule' && (github.event_name != 'workflow_dispatch' || inputs.strategy-fail-fast) }}
matrix:
feature: [
"apm",
"browsers",
"build-essential",
"claude-code",
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Below is a list with included features, click on the link for more details.

| Name | Description |
| --- | --- |
| [apm](./features/src/apm/README.md) | Installs APM (Agent Package Manager). |
| [browsers](./features/src/browsers/README.md) | Installs various browsers and their dependencies. |
| [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. |
Expand Down
8 changes: 6 additions & 2 deletions build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ func init() {
////////// publish features
gotaskr.Task("Features:Publish", func() error { return publishFeatures() })

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

////////// browsers
gotaskr.Task("Feature:browsers:Package", func() error { return packageFeature("browsers") })
gotaskr.Task("Feature:browsers:Test", func() error { return testFeature("browsers") })
Expand Down Expand Up @@ -379,11 +383,11 @@ func testFeature(featureName string) error {
}

// Write the Dockerfile
if err := os.WriteFile(path.Join(devcontainerPath, "Dockerfile"), []byte(fmt.Sprintf(`
if err := os.WriteFile(path.Join(devcontainerPath, "Dockerfile"), fmt.Appendf(nil, `
FROM %s
ADD check.sh /tmp/check.sh
ADD functions.sh /tmp/functions.sh
`, testImage)), os.ModePerm); err != nil {
`, testImage), os.ModePerm); err != nil {
return err
}

Expand Down
13 changes: 13 additions & 0 deletions features/src/apm/NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Notes

### System Compatibility

Debian

### Accessed Urls

Needs access to the following URL for downloading:
* https://github.com

Needs access to the following URL for resolving:
* https://api.github.com
35 changes: 35 additions & 0 deletions features/src/apm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# APM (Agent Package Manager) (apm)

Installs APM (Agent Package Manager).

## Example Usage

```json
"features": {
"ghcr.io/postfinance/devcontainer-features/apm:1.0.0": {
"version": "latest",
"downloadUrl": ""
}
}
```

## Options

| Option | Description | Type | Default Value | Proposals |
|-----|-----|-----|-----|-----|
| version | The version of APM to install. | string | latest | latest, 0.26.0, 0.25.0 |
| downloadUrl | The download URL to use for APM binaries. | string | <empty> | https://mycompany.com/artifactory/github-releases-remote |

## Notes

### System Compatibility

Debian

### Accessed Urls

Needs access to the following URL for downloading:
* https://github.com

Needs access to the following URL for resolving:
* https://api.github.com
26 changes: 26 additions & 0 deletions features/src/apm/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"id": "apm",
"version": "1.0.0",
"name": "APM (Agent Package Manager)",
"description": "Installs APM (Agent Package Manager).",
"options": {
"version": {
"type": "string",
"proposals": [
"latest",
"0.26.0",
"0.25.0"
],
"default": "latest",
"description": "The version of APM to install."
},
"downloadUrl": {
"type": "string",
"default": "",
"proposals": [
"https://mycompany.com/artifactory/github-releases-remote"
],
"description": "The download URL to use for APM binaries."
}
}
}
5 changes: 5 additions & 0 deletions features/src/apm/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
. ./functions.sh

"./installer_$(detect_arch)" \
-version="${VERSION:-"latest"}" \
-downloadUrl="${DOWNLOADURL:-""}"
136 changes: 136 additions & 0 deletions features/src/apm/installer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package main

import (
"builder/installer"
"flag"
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"

"github.com/roemer/gover"
)

//////////
// Configuration
//////////

var versionRegex *regexp.Regexp = regexp.MustCompile(`(?m)^v(?P<raw>(\d+)\.(\d+)\.(\d+))$`)

//////////
// Main
//////////

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

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)
}
}

// Handle the flags
version := flag.String("version", "latest", "")
downloadUrl := flag.String("downloadUrl", "", "")
flag.Parse()

// Load settings from an external file
if err := installer.LoadOverrides(); err != nil {
return err
}

// Apply override logic for URLs
installer.HandleGitHubOverride(downloadUrl, "microsoft/apm", "apm-download-url")

// Create and process the feature
feature := installer.NewFeature("APM", true,
&apmComponent{
ComponentBase: installer.NewComponentBase("APM", *version),
DownloadUrl: *downloadUrl,
})
return feature.Process()
}

//////////
// Implementation
//////////

type apmComponent struct {
*installer.ComponentBase
DownloadUrl string
}

func (c *apmComponent) GetAllVersions() ([]*gover.Version, error) {
tags, err := installer.Tools.GitHub.GetTags("microsoft", "apm")
if err != nil {
return nil, err
}
return installer.Tools.Versioning.ParseVersionsFromList(tags, versionRegex, true)
}

func (c *apmComponent) InstallVersion(version *gover.Version) error {
archPart, err := installer.Tools.System.MapArchitecture(map[string]string{
installer.AMD64: "x86_64",
installer.ARM64: "arm64",
})
if err != nil {
return err
}

// Download the archive
archiveName := fmt.Sprintf("apm-linux-%s", archPart)
fileName := archiveName + ".tar.gz"
downloadUrl, err := installer.Tools.Http.BuildUrl(c.DownloadUrl, "v"+version.Raw, fileName)
if err != nil {
return err
}
if err := installer.Tools.Download.ToFile(downloadUrl, fileName, "APM"); err != nil {
return err
}
defer os.Remove(fileName)

// Extract to a temp directory
tempDir, err := os.MkdirTemp("", "apm-extract")
if err != nil {
return err
}
defer os.RemoveAll(tempDir)

if err := installer.Tools.Compression.ExtractTarGz(fileName, tempDir, false); err != nil {
return err
}

// Move the extracted archive directory to /opt/apm to preserve the depencencies
// Can be simplified if the release is changed to a single binary
installDir := "/opt/apm"
if err := os.RemoveAll(installDir); err != nil && !os.IsNotExist(err) {
return err
}
if err := os.MkdirAll(installDir, 0755); err != nil {
return err
}
extractedDir := filepath.Join(tempDir, archiveName)
if err := installer.Tools.FileSystem.MoveFolder(extractedDir, installDir, false); err != nil {
return err
}

// Create a symlink from /usr/local/bin/apm to the installed binary
binaryPath := filepath.Join(installDir, "apm")
return installer.Tools.FileSystem.CreateSymLink(binaryPath, "/usr/local/bin/apm", false)
}
8 changes: 8 additions & 0 deletions features/test/apm/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/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_file_exists "/usr/local/bin/apm"
check_version "$(apm --version)" "0.26.0"
12 changes: 12 additions & 0 deletions features/test/apm/scenarios.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"install": {
"build": {
"dockerfile": "Dockerfile"
},
"features": {
"./apm": {
"version": "0.26.0"
}
}
}
}
3 changes: 3 additions & 0 deletions features/test/apm/test-images.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
"mcr.microsoft.com/devcontainers/base:debian13"
]
3 changes: 3 additions & 0 deletions override-all.env
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ DEV_FEATURE_OVERRIDE_GITLAB_DOWNLOAD_URL=""

###### Feature-Specific Overrides

# apm
APM_DOWNLOAD_URL=""

# browsers
CHROME_DOWNLOAD_URL=""
CHROME_VERSIONS_URL=""
Expand Down