From 1fb95ae4ab058f192a8ecd5a6be28d7c2b7072fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 08:23:46 +0000 Subject: [PATCH 1/5] Initial plan From 7b393fbece52a3f6900c036164b48a3712378107 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 08:32:10 +0000 Subject: [PATCH 2/5] Add APM (Agent Package Manager) devcontainer feature --- features/src/apm/NOTES.md | 13 +++ features/src/apm/README.md | 35 +++++++ features/src/apm/devcontainer-feature.json | 26 +++++ features/src/apm/install.sh | 5 + features/src/apm/installer.go | 105 +++++++++++++++++++++ features/test/apm/install.sh | 8 ++ features/test/apm/scenarios.json | 12 +++ features/test/apm/test-images.json | 7 ++ override-all.env | 3 + 9 files changed, 214 insertions(+) create mode 100644 features/src/apm/NOTES.md create mode 100755 features/src/apm/README.md create mode 100644 features/src/apm/devcontainer-feature.json create mode 100644 features/src/apm/install.sh create mode 100644 features/src/apm/installer.go create mode 100644 features/test/apm/install.sh create mode 100644 features/test/apm/scenarios.json create mode 100644 features/test/apm/test-images.json diff --git a/features/src/apm/NOTES.md b/features/src/apm/NOTES.md new file mode 100644 index 0000000..30988a2 --- /dev/null +++ b/features/src/apm/NOTES.md @@ -0,0 +1,13 @@ +## Notes + +### System Compatibility + +Debian, Ubuntu, Alpine + +### 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 diff --git a/features/src/apm/README.md b/features/src/apm/README.md new file mode 100755 index 0000000..b7e10cc --- /dev/null +++ b/features/src/apm/README.md @@ -0,0 +1,35 @@ +# APM (Agent Package Manager) (apm) + +Installs APM (Agent Package Manager) from https://github.com/microsoft/apm/releases. + +## 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, Ubuntu, Alpine + +### 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 diff --git a/features/src/apm/devcontainer-feature.json b/features/src/apm/devcontainer-feature.json new file mode 100644 index 0000000..d96a7ab --- /dev/null +++ b/features/src/apm/devcontainer-feature.json @@ -0,0 +1,26 @@ +{ + "id": "apm", + "version": "1.0.0", + "name": "APM (Agent Package Manager)", + "description": "Installs APM (Agent Package Manager) from https://github.com/microsoft/apm/releases.", + "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." + } + } +} diff --git a/features/src/apm/install.sh b/features/src/apm/install.sh new file mode 100644 index 0000000..13b2b54 --- /dev/null +++ b/features/src/apm/install.sh @@ -0,0 +1,5 @@ +. ./functions.sh + +"./installer_$(detect_arch)" \ + -version="${VERSION:-"latest"}" \ + -downloadUrl="${DOWNLOADURL:-""}" diff --git a/features/src/apm/installer.go b/features/src/apm/installer.go new file mode 100644 index 0000000..f8779cb --- /dev/null +++ b/features/src/apm/installer.go @@ -0,0 +1,105 @@ +package main + +import ( + "builder/installer" + "flag" + "fmt" + "os" + "path/filepath" + "regexp" + + "github.com/roemer/gover" +) + +////////// +// Configuration +////////// + +var versionRegex *regexp.Regexp = regexp.MustCompile(`(?m)^v(?P(\d+)\.(\d+)\.(\d+))$`) + +////////// +// Main +////////// + +func main() { + if err := runMain(); err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } +} + +func runMain() error { + // 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 + } + + // Install the binary (located in apm-linux-{arch}/apm inside the archive) + return installer.Tools.System.InstallBinaryToUsrLocalBin(filepath.Join(tempDir, archiveName, "apm"), "apm") +} diff --git a/features/test/apm/install.sh b/features/test/apm/install.sh new file mode 100644 index 0000000..e5b6cd3 --- /dev/null +++ b/features/test/apm/install.sh @@ -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" diff --git a/features/test/apm/scenarios.json b/features/test/apm/scenarios.json new file mode 100644 index 0000000..7a334e6 --- /dev/null +++ b/features/test/apm/scenarios.json @@ -0,0 +1,12 @@ +{ + "install": { + "build": { + "dockerfile": "Dockerfile" + }, + "features": { + "./apm": { + "version": "0.26.0" + } + } + } +} diff --git a/features/test/apm/test-images.json b/features/test/apm/test-images.json new file mode 100644 index 0000000..d2c2bf7 --- /dev/null +++ b/features/test/apm/test-images.json @@ -0,0 +1,7 @@ +[ + "mcr.microsoft.com/devcontainers/base:alpine", + "mcr.microsoft.com/devcontainers/base:debian12", + "mcr.microsoft.com/devcontainers/base:debian13", + "mcr.microsoft.com/devcontainers/base:ubuntu24.04", + "mcr.microsoft.com/devcontainers/base:ubuntu26.04" +] diff --git a/override-all.env b/override-all.env index 78e5f07..fb529f2 100644 --- a/override-all.env +++ b/override-all.env @@ -72,6 +72,9 @@ KUBECTL_KUBESCORE_DOWNLOAD_URL="" # nginx NGINX_DOWNLOAD_URL="" +# apm +APM_DOWNLOAD_URL="" + # claude-code CLAUDE_CODE_DOWNLOAD_URL="" From 1f3f4a0c8d32a6182b2987fec54e83dfc67024c3 Mon Sep 17 00:00:00 2001 From: Roemer Date: Tue, 28 Jul 2026 09:07:21 +0000 Subject: [PATCH 3/5] fix: apm binary dependencies, ci --- .github/workflows/ci.yml | 1 + README.md | 1 + build/build.go | 4 ++++ features/src/apm/README.md | 2 +- features/src/apm/devcontainer-feature.json | 2 +- features/src/apm/installer.go | 19 +++++++++++++++++-- override-all.env | 6 +++--- 7 files changed, 28 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73ded42..b3c2848 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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", diff --git a/README.md b/README.md index d5a57ab..f352b5e 100644 --- a/README.md +++ b/README.md @@ -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. | diff --git a/build/build.go b/build/build.go index a89d977..9dd025d 100644 --- a/build/build.go +++ b/build/build.go @@ -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") }) diff --git a/features/src/apm/README.md b/features/src/apm/README.md index b7e10cc..b2bc121 100755 --- a/features/src/apm/README.md +++ b/features/src/apm/README.md @@ -1,6 +1,6 @@ # APM (Agent Package Manager) (apm) -Installs APM (Agent Package Manager) from https://github.com/microsoft/apm/releases. +Installs APM (Agent Package Manager). ## Example Usage diff --git a/features/src/apm/devcontainer-feature.json b/features/src/apm/devcontainer-feature.json index d96a7ab..9d650b9 100644 --- a/features/src/apm/devcontainer-feature.json +++ b/features/src/apm/devcontainer-feature.json @@ -2,7 +2,7 @@ "id": "apm", "version": "1.0.0", "name": "APM (Agent Package Manager)", - "description": "Installs APM (Agent Package Manager) from https://github.com/microsoft/apm/releases.", + "description": "Installs APM (Agent Package Manager).", "options": { "version": { "type": "string", diff --git a/features/src/apm/installer.go b/features/src/apm/installer.go index f8779cb..20828d9 100644 --- a/features/src/apm/installer.go +++ b/features/src/apm/installer.go @@ -100,6 +100,21 @@ func (c *apmComponent) InstallVersion(version *gover.Version) error { return err } - // Install the binary (located in apm-linux-{arch}/apm inside the archive) - return installer.Tools.System.InstallBinaryToUsrLocalBin(filepath.Join(tempDir, archiveName, "apm"), "apm") + // 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) } diff --git a/override-all.env b/override-all.env index fb529f2..fcaee5e 100644 --- a/override-all.env +++ b/override-all.env @@ -8,6 +8,9 @@ DEV_FEATURE_OVERRIDE_GITLAB_DOWNLOAD_URL="" ###### Feature-Specific Overrides +# apm +APM_DOWNLOAD_URL="" + # browsers CHROME_DOWNLOAD_URL="" CHROME_VERSIONS_URL="" @@ -72,9 +75,6 @@ KUBECTL_KUBESCORE_DOWNLOAD_URL="" # nginx NGINX_DOWNLOAD_URL="" -# apm -APM_DOWNLOAD_URL="" - # claude-code CLAUDE_CODE_DOWNLOAD_URL="" From dac2a6b584fea0988418c1f49e6365e5a158ea61 Mon Sep 17 00:00:00 2001 From: Roemer Date: Tue, 28 Jul 2026 09:47:12 +0000 Subject: [PATCH 4/5] fix: Remove alpine support --- build/build.go | 4 ++-- features/src/apm/NOTES.md | 2 +- features/src/apm/README.md | 2 +- features/test/apm/test-images.json | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/build/build.go b/build/build.go index 9dd025d..1ead11a 100644 --- a/build/build.go +++ b/build/build.go @@ -383,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 } diff --git a/features/src/apm/NOTES.md b/features/src/apm/NOTES.md index 30988a2..d0ef26a 100644 --- a/features/src/apm/NOTES.md +++ b/features/src/apm/NOTES.md @@ -2,7 +2,7 @@ ### System Compatibility -Debian, Ubuntu, Alpine +Debian, Ubuntu ### Accessed Urls diff --git a/features/src/apm/README.md b/features/src/apm/README.md index b2bc121..2029458 100755 --- a/features/src/apm/README.md +++ b/features/src/apm/README.md @@ -24,7 +24,7 @@ Installs APM (Agent Package Manager). ### System Compatibility -Debian, Ubuntu, Alpine +Debian, Ubuntu ### Accessed Urls diff --git a/features/test/apm/test-images.json b/features/test/apm/test-images.json index d2c2bf7..7e091f0 100644 --- a/features/test/apm/test-images.json +++ b/features/test/apm/test-images.json @@ -1,5 +1,4 @@ [ - "mcr.microsoft.com/devcontainers/base:alpine", "mcr.microsoft.com/devcontainers/base:debian12", "mcr.microsoft.com/devcontainers/base:debian13", "mcr.microsoft.com/devcontainers/base:ubuntu24.04", From fb43571dc793eb69390103e246f36a070354a661 Mon Sep 17 00:00:00 2001 From: Roemer Date: Tue, 28 Jul 2026 10:06:52 +0000 Subject: [PATCH 5/5] fix: Remove ubuntu support, check debian version --- features/src/apm/NOTES.md | 2 +- features/src/apm/README.md | 2 +- features/src/apm/installer.go | 16 ++++++++++++++++ features/test/apm/test-images.json | 5 +---- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/features/src/apm/NOTES.md b/features/src/apm/NOTES.md index d0ef26a..ba5ef94 100644 --- a/features/src/apm/NOTES.md +++ b/features/src/apm/NOTES.md @@ -2,7 +2,7 @@ ### System Compatibility -Debian, Ubuntu +Debian ### Accessed Urls diff --git a/features/src/apm/README.md b/features/src/apm/README.md index 2029458..4eb7253 100755 --- a/features/src/apm/README.md +++ b/features/src/apm/README.md @@ -24,7 +24,7 @@ Installs APM (Agent Package Manager). ### System Compatibility -Debian, Ubuntu +Debian ### Accessed Urls diff --git a/features/src/apm/installer.go b/features/src/apm/installer.go index 20828d9..cb78da4 100644 --- a/features/src/apm/installer.go +++ b/features/src/apm/installer.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "regexp" + "strconv" "github.com/roemer/gover" ) @@ -29,6 +30,21 @@ 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) + } + } + // Handle the flags version := flag.String("version", "latest", "") downloadUrl := flag.String("downloadUrl", "", "") diff --git a/features/test/apm/test-images.json b/features/test/apm/test-images.json index 7e091f0..40a34e5 100644 --- a/features/test/apm/test-images.json +++ b/features/test/apm/test-images.json @@ -1,6 +1,3 @@ [ - "mcr.microsoft.com/devcontainers/base:debian12", - "mcr.microsoft.com/devcontainers/base:debian13", - "mcr.microsoft.com/devcontainers/base:ubuntu24.04", - "mcr.microsoft.com/devcontainers/base:ubuntu26.04" + "mcr.microsoft.com/devcontainers/base:debian13" ]