From 561ab62799560c4a34e60d0aadfcd1a616552029 Mon Sep 17 00:00:00 2001 From: Roemer Date: Thu, 22 Jan 2026 13:55:11 +0000 Subject: [PATCH] feat: Add terraform --- .github/workflows/ci.yml | 1 + README.md | 1 + build/build.go | 5 + features/src/terraform/README.md | 23 ++++ .../src/terraform/devcontainer-feature.json | 31 +++++ features/src/terraform/install.sh | 6 + features/src/terraform/installer.go | 122 ++++++++++++++++++ features/test/terraform/install.sh | 8 ++ features/test/terraform/scenarios.json | 15 +++ features/test/terraform/test-images.json | 6 + override-all.env | 4 + 11 files changed, 222 insertions(+) create mode 100755 features/src/terraform/README.md create mode 100644 features/src/terraform/devcontainer-feature.json create mode 100755 features/src/terraform/install.sh create mode 100644 features/src/terraform/installer.go create mode 100755 features/test/terraform/install.sh create mode 100644 features/test/terraform/scenarios.json create mode 100644 features/test/terraform/test-images.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d552a4..61c2aa9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,7 @@ jobs: "python", "sonar-scanner-cli", "system-packages", + "terraform", "timezone", "vault-cli", "zig" diff --git a/README.md b/README.md index 32ce2a0..b68752d 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Below is a list with included features, click on the link for more details. | [python](./features/src/python/README.md) | Installs Python. | | [sonar-scanner-cli](./features/src/sonar-scanner-cli/README.md) | Installs the SonarScanner CLI. | | [system-packages](./features/src/system-packages/README.md) | Install arbitrary system packages using the system package manager. | +| [terraform](./features/src/terraform/README.md) | Installs the Terraform CLI. | | [timezone](./features/src/timezone/README.md) | Allows setting the timezone. | | [vault-cli](./features/src/vault-cli/README.md) | Installs the Vault CLI. | | [zig](./features/src/zig/README.md) | Installs Zig. | diff --git a/build/build.go b/build/build.go index 4dc885b..2699cf6 100644 --- a/build/build.go +++ b/build/build.go @@ -190,6 +190,11 @@ func init() { gotaskr.Task("Feature:system-packages:Test", func() error { return testFeature("system-packages") }) gotaskr.Task("Feature:system-packages:Publish", func() error { return publishFeature("system-packages") }) + ////////// terraform + gotaskr.Task("Feature:terraform:Package", func() error { return packageFeature("terraform") }) + gotaskr.Task("Feature:terraform:Test", func() error { return testFeature("terraform") }) + gotaskr.Task("Feature:terraform:Publish", func() error { return publishFeature("terraform") }) + ////////// timezone gotaskr.Task("Feature:timezone:Package", func() error { return packageFeature("timezone") }) gotaskr.Task("Feature:timezone:Test", func() error { return testFeature("timezone") }) diff --git a/features/src/terraform/README.md b/features/src/terraform/README.md new file mode 100755 index 0000000..8d9d121 --- /dev/null +++ b/features/src/terraform/README.md @@ -0,0 +1,23 @@ +# Terraform (terraform) + +Installs the Terraform CLI. + +## Example Usage + +```json +"features": { + "ghcr.io/postfinance/devcontainer-features/terraform:0.1.0": { + "version": "latest", + "downloadUrl": "", + "versionsUrl": "" + } +} +``` + +## Options + +| Option | Description | Type | Default Value | Proposals | +|-----|-----|-----|-----|-----| +| version | The version of Terraform CLI to install. | string | latest | latest, 1.14.3, 1 | +| downloadUrl | The download URL to use for Terraform CLI binaries. | string | <empty> | https://mycompany.com/artifactory/hashicorp-releases-generic-remote/terraform | +| versionsUrl | The URL to fetch the available Terraform CLI versions from. | string | <empty> | | diff --git a/features/src/terraform/devcontainer-feature.json b/features/src/terraform/devcontainer-feature.json new file mode 100644 index 0000000..40f20e1 --- /dev/null +++ b/features/src/terraform/devcontainer-feature.json @@ -0,0 +1,31 @@ +{ + "id": "terraform", + "version": "0.1.0", + "name": "Terraform", + "description": "Installs the Terraform CLI.", + "options": { + "version": { + "type": "string", + "proposals": [ + "latest", + "1.14.3", + "1" + ], + "default": "latest", + "description": "The version of Terraform CLI to install." + }, + "downloadUrl": { + "type": "string", + "default": "", + "proposals": [ + "https://mycompany.com/artifactory/hashicorp-releases-generic-remote/terraform" + ], + "description": "The download URL to use for Terraform CLI binaries." + }, + "versionsUrl": { + "type": "string", + "default": "", + "description": "The URL to fetch the available Terraform CLI versions from." + } + } +} diff --git a/features/src/terraform/install.sh b/features/src/terraform/install.sh new file mode 100755 index 0000000..d66ec95 --- /dev/null +++ b/features/src/terraform/install.sh @@ -0,0 +1,6 @@ +. ./functions.sh + +"./installer_$(detect_arch)" \ + -version="${VERSION:-"latest"}" \ + -downloadUrl="${DOWNLOADURL:-""}" \ + -versionsUrl="${VERSIONSURL:-""}" diff --git a/features/src/terraform/installer.go b/features/src/terraform/installer.go new file mode 100644 index 0000000..da69112 --- /dev/null +++ b/features/src/terraform/installer.go @@ -0,0 +1,122 @@ +package main + +import ( + "builder/installer" + "encoding/json" + "flag" + "fmt" + "os" + "regexp" + + "github.com/roemer/gover" +) + +////////// +// Configuration +////////// + +var versionRegexp *regexp.Regexp = regexp.MustCompile(`(?m:)^(\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", "", "") + versionsUrl := flag.String("versionsUrl", "", "") + flag.Parse() + + // Load settings from an external file + if err := installer.LoadOverrides(); err != nil { + return err + } + + installer.HandleOverride(downloadUrl, "https://releases.hashicorp.com/terraform", "terraform-download-url") + installer.HandleOverride(versionsUrl, "https://releases.hashicorp.com/terraform/index.json", "terraform-versions-url") + + // Create and process the feature + feature := installer.NewFeature("Terraform", true, + &terraformComponent{ + ComponentBase: installer.NewComponentBase("Terraform", *version), + DownloadUrl: *downloadUrl, + VersionsUrl: *versionsUrl, + }) + return feature.Process() +} + +////////// +// Implementation +////////// + +type terraformComponent struct { + *installer.ComponentBase + DownloadUrl string + VersionsUrl string +} + +func (c *terraformComponent) GetAllVersions() ([]*gover.Version, error) { + versionFileContent, err := installer.Tools.Download.AsBytes(c.VersionsUrl) + if err != nil { + return nil, err + } + var jsonData map[string]any + if err := json.Unmarshal(versionFileContent, &jsonData); err != nil { + return nil, err + } + versionsObj, ok := jsonData["versions"].(map[string]any) + if !ok { + return nil, fmt.Errorf("versions not found in terraform index json") + } + versions := []*gover.Version{} + for versionString := range versionsObj { + version, err := gover.ParseVersionFromRegex(versionString, versionRegexp) + if err != nil { + continue // skip invalid + } + versions = append(versions, version) + } + return versions, nil +} + +func (c *terraformComponent) InstallVersion(version *gover.Version) error { + archPart, err := installer.Tools.System.MapArchitecture(map[string]string{ + installer.AMD64: "amd64", + installer.ARM64: "arm64", + }) + if err != nil { + return err + } + fileName := fmt.Sprintf("terraform_%s_linux_%s.zip", version.Raw, archPart) + downloadUrl, err := installer.Tools.Http.BuildUrl(c.DownloadUrl, version.Raw, fileName) + if err != nil { + return err + } + if err := installer.Tools.Download.ToFile(downloadUrl, fileName, "Terraform"); err != nil { + return err + } + // Extract it + if err := installer.Tools.Compression.ExtractZip(fileName, "terraform", false); err != nil { + return err + } + // Install + if err := installer.Tools.System.InstallBinaryToUsrLocalBin("terraform/terraform", "terraform"); err != nil { + return err + } + // Cleanup + if err := os.Remove(fileName); err != nil { + return err + } + if err := os.RemoveAll("terraform"); err != nil { + return err + } + return nil +} diff --git a/features/test/terraform/install.sh b/features/test/terraform/install.sh new file mode 100755 index 0000000..2e701c7 --- /dev/null +++ b/features/test/terraform/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_command_exists terraform +check_version "$(terraform -v)" "1.14.3" diff --git a/features/test/terraform/scenarios.json b/features/test/terraform/scenarios.json new file mode 100644 index 0000000..45407d5 --- /dev/null +++ b/features/test/terraform/scenarios.json @@ -0,0 +1,15 @@ +{ + "install": { + "build": { + "dockerfile": "Dockerfile", + "options": [ + "--add-host=host.docker.internal:host-gateway" + ] + }, + "features": { + "./terraform": { + "version": "1.14.3" + } + } + } +} \ No newline at end of file diff --git a/features/test/terraform/test-images.json b/features/test/terraform/test-images.json new file mode 100644 index 0000000..f4cf196 --- /dev/null +++ b/features/test/terraform/test-images.json @@ -0,0 +1,6 @@ +[ + "mcr.microsoft.com/devcontainers/base:debian-11", + "mcr.microsoft.com/devcontainers/base:debian-12", + "mcr.microsoft.com/devcontainers/base:alpine", + "mcr.microsoft.com/devcontainers/base:ubuntu-24.04" +] \ No newline at end of file diff --git a/override-all.env b/override-all.env index 480df61..862c9c4 100644 --- a/override-all.env +++ b/override-all.env @@ -68,6 +68,10 @@ PYTHON_PIP_INDEX="" PYTHON_PIP_INDEX_URL="" PYTHON_PIP_TRUSTED_HOST="" +# terraform +TERRAFORM_DOWNLOAD_URL="" +TERRAFORM_VERSIONS_URL="" + # vault-cli VAULT_CLI_DOWNLOAD_URL="" VAULT_CLI_VERSIONS_URL=""