From cc911a67fa4b231f5a449ce74aa0aeb549baab82 Mon Sep 17 00:00:00 2001 From: Roemer Date: Fri, 16 Jan 2026 13:14:38 +0000 Subject: [PATCH 1/2] feat: Add kubectl feature --- .github/workflows/ci.yml | 1 + README.md | 25 + build/build.go | 293 +++------- features/src/kubectl/README.md | 49 ++ .../src/kubectl/devcontainer-feature.json | 134 +++++ features/src/kubectl/install.sh | 19 + features/src/kubectl/installer.go | 536 ++++++++++++++++++ features/test/kubectl/full.sh | 14 + features/test/kubectl/install.sh | 7 + features/test/kubectl/scenarios.json | 42 ++ features/test/kubectl/test-images.json | 6 + go.mod | 20 +- go.sum | 30 +- installer/github.go | 13 +- override-all.env | 10 + 15 files changed, 960 insertions(+), 239 deletions(-) create mode 100755 features/src/kubectl/README.md create mode 100644 features/src/kubectl/devcontainer-feature.json create mode 100755 features/src/kubectl/install.sh create mode 100644 features/src/kubectl/installer.go create mode 100755 features/test/kubectl/full.sh create mode 100755 features/test/kubectl/install.sh create mode 100644 features/test/kubectl/scenarios.json create mode 100644 features/test/kubectl/test-images.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6b4379..2d552a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,7 @@ jobs: "goreleaser", "instant-client", "jfrog-cli", + "kubectl", "locale", "make", "mingw", diff --git a/README.md b/README.md index db04229..32ce2a0 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Below is a list with included features, click on the link for more details. | [goreleaser](./features/src/goreleaser/README.md) | Installs GoReleaser. | | [instant-client](./features/src/instant-client/README.md) | Installs the Oracle Instant Client Basic package. | | [jfrog-cli](./features/src/jfrog-cli/README.md) | Installs the JFrog CLI. | +| [kubectl](./features/src/kubectl/README.md) | Installs kubectl and other tools for managing kubernetes. | | [locale](./features/src/locale/README.md) | Allows setting the locale. | | [make](./features/src/make/README.md) | Installs Make. | | [mingw](./features/src/mingw/README.md) | Installs MinGW. | @@ -42,6 +43,11 @@ Below is a list with included features, click on the link for more details. ## Usage +### Tokens + +Some version lookups depend on APIs with a rate limit. Use the following environment variables to set api tokens to increase those limits: +* `DEV_FEATURE_TOKEN_GITHUB_API` - Set a token to use the GitHub API. + ### Versions Most features allow you to define one or more versions of the software that should be installed by the feature. @@ -110,3 +116,22 @@ Then set this in your feature to explicitly unset it: ### Extend an existing feature TBD + +## Development + +### Testing features + +To test a feature, you can compile it and copy or mount the installer into a container with root privileges. + +Here is an example to compile a feature: +```go +go build -o kubectl.bin features/src/kubectl/installer.go +``` +Then you can run a simple docker container which mounts the folder with the installer and then just run the installer: +```bash +docker run --rm -it -v $PWD:/data debian /bin/bash +``` +and then run the installer: +```bash +/data/kubectl.bin --version=1.35.0 +``` diff --git a/build/build.go b/build/build.go index 67bdf62..4dc885b 100644 --- a/build/build.go +++ b/build/build.go @@ -81,268 +81,129 @@ func init() { }) ////////// browsers - gotaskr.Task("Feature:browsers:Package", func() error { - return packageFeature("browsers") - }) - gotaskr.Task("Feature:browsers:Test", func() error { - return testFeature("browsers") - }) - gotaskr.Task("Feature:browsers:Publish", func() error { - return publishFeature("browsers") - }) + gotaskr.Task("Feature:browsers:Package", func() error { return packageFeature("browsers") }) + gotaskr.Task("Feature:browsers:Test", func() error { return testFeature("browsers") }) + gotaskr.Task("Feature:browsers:Publish", func() error { return publishFeature("browsers") }) ////////// build-essential - gotaskr.Task("Feature:build-essential:Package", func() error { - return packageFeature("build-essential") - }) - gotaskr.Task("Feature:build-essential:Test", func() error { - return testFeature("build-essential") - }) - gotaskr.Task("Feature:build-essential:Publish", func() error { - return publishFeature("build-essential") - }) + gotaskr.Task("Feature:build-essential:Package", func() error { return packageFeature("build-essential") }) + gotaskr.Task("Feature:build-essential:Test", func() error { return testFeature("build-essential") }) + gotaskr.Task("Feature:build-essential:Publish", func() error { return publishFeature("build-essential") }) ////////// cypress-deps - gotaskr.Task("Feature:cypress-deps:Package", func() error { - return packageFeature("cypress-deps") - }) - gotaskr.Task("Feature:cypress-deps:Test", func() error { - return testFeature("cypress-deps") - }) - gotaskr.Task("Feature:cypress-deps:Publish", func() error { - return publishFeature("cypress-deps") - }) + gotaskr.Task("Feature:cypress-deps:Package", func() error { return packageFeature("cypress-deps") }) + gotaskr.Task("Feature:cypress-deps:Test", func() error { return testFeature("cypress-deps") }) + gotaskr.Task("Feature:cypress-deps:Publish", func() error { return publishFeature("cypress-deps") }) ////////// 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") - }) - gotaskr.Task("Feature:docker-out:Publish", func() error { - return publishFeature("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") }) + gotaskr.Task("Feature:docker-out:Publish", func() error { return publishFeature("docker-out") }) ////////// eclipse-deps - gotaskr.Task("Feature:eclipse-deps:Package", func() error { - return packageFeature("eclipse-deps") - }) - gotaskr.Task("Feature:eclipse-deps:Test", func() error { - return testFeature("eclipse-deps") - }) - gotaskr.Task("Feature:eclipse-deps:Publish", func() error { - return publishFeature("eclipse-deps") - }) + gotaskr.Task("Feature:eclipse-deps:Package", func() error { return packageFeature("eclipse-deps") }) + gotaskr.Task("Feature:eclipse-deps:Test", func() error { return testFeature("eclipse-deps") }) + gotaskr.Task("Feature:eclipse-deps:Publish", func() error { return publishFeature("eclipse-deps") }) ////////// git-lfs - gotaskr.Task("Feature:git-lfs:Package", func() error { - return packageFeature("git-lfs") - }) - gotaskr.Task("Feature:git-lfs:Test", func() error { - return testFeature("git-lfs") - }) - gotaskr.Task("Feature:git-lfs:Publish", func() error { - return publishFeature("git-lfs") - }) + gotaskr.Task("Feature:git-lfs:Package", func() error { return packageFeature("git-lfs") }) + gotaskr.Task("Feature:git-lfs:Test", func() error { return testFeature("git-lfs") }) + gotaskr.Task("Feature:git-lfs:Publish", func() error { return publishFeature("git-lfs") }) ////////// gitlab-cli - gotaskr.Task("Feature:gitlab-cli:Package", func() error { - return packageFeature("gitlab-cli") - }) - gotaskr.Task("Feature:gitlab-cli:Test", func() error { - return testFeature("gitlab-cli") - }) - gotaskr.Task("Feature:gitlab-cli:Publish", func() error { - return publishFeature("gitlab-cli") - }) + gotaskr.Task("Feature:gitlab-cli:Package", func() error { return packageFeature("gitlab-cli") }) + gotaskr.Task("Feature:gitlab-cli:Test", func() error { return testFeature("gitlab-cli") }) + gotaskr.Task("Feature:gitlab-cli:Publish", func() error { return publishFeature("gitlab-cli") }) ////////// go - gotaskr.Task("Feature:go:Package", func() error { - return packageFeature("go") - }) - gotaskr.Task("Feature:go:Test", func() error { - return testFeature("go") - }) - gotaskr.Task("Feature:go:Publish", func() error { - return publishFeature("go") - }) + gotaskr.Task("Feature:go:Package", func() error { return packageFeature("go") }) + gotaskr.Task("Feature:go:Test", func() error { return testFeature("go") }) + gotaskr.Task("Feature:go:Publish", func() error { return publishFeature("go") }) ////////// gonovate - gotaskr.Task("Feature:gonovate:Package", func() error { - return packageFeature("gonovate") - }) - gotaskr.Task("Feature:gonovate:Test", func() error { - return testFeature("gonovate") - }) - gotaskr.Task("Feature:gonovate:Publish", func() error { - return publishFeature("gonovate") - }) + gotaskr.Task("Feature:gonovate:Package", func() error { return packageFeature("gonovate") }) + gotaskr.Task("Feature:gonovate:Test", func() error { return testFeature("gonovate") }) + gotaskr.Task("Feature:gonovate:Publish", func() error { return publishFeature("gonovate") }) ////////// goreleaser - gotaskr.Task("Feature:goreleaser:Package", func() error { - return packageFeature("goreleaser") - }) - gotaskr.Task("Feature:goreleaser:Test", func() error { - return testFeature("goreleaser") - }) - gotaskr.Task("Feature:goreleaser:Publish", func() error { - return publishFeature("goreleaser") - }) + gotaskr.Task("Feature:goreleaser:Package", func() error { return packageFeature("goreleaser") }) + gotaskr.Task("Feature:goreleaser:Test", func() error { return testFeature("goreleaser") }) + gotaskr.Task("Feature:goreleaser:Publish", func() error { return publishFeature("goreleaser") }) ////////// instant-client - gotaskr.Task("Feature:instant-client:Package", func() error { - return packageFeature("instant-client") - }) - gotaskr.Task("Feature:instant-client:Test", func() error { - return testFeature("instant-client") - }) - gotaskr.Task("Feature:instant-client:Publish", func() error { - return publishFeature("instant-client") - }) + gotaskr.Task("Feature:instant-client:Package", func() error { return packageFeature("instant-client") }) + gotaskr.Task("Feature:instant-client:Test", func() error { return testFeature("instant-client") }) + gotaskr.Task("Feature:instant-client:Publish", func() error { return publishFeature("instant-client") }) ////////// jfrog-cli - gotaskr.Task("Feature:jfrog-cli:Package", func() error { - return packageFeature("jfrog-cli") - }) - gotaskr.Task("Feature:jfrog-cli:Test", func() error { - return testFeature("jfrog-cli") - }) - gotaskr.Task("Feature:jfrog-cli:Publish", func() error { - return publishFeature("jfrog-cli") - }) + gotaskr.Task("Feature:jfrog-cli:Package", func() error { return packageFeature("jfrog-cli") }) + gotaskr.Task("Feature:jfrog-cli:Test", func() error { return testFeature("jfrog-cli") }) + gotaskr.Task("Feature:jfrog-cli:Publish", func() error { return publishFeature("jfrog-cli") }) + + ////////// kubectl + gotaskr.Task("Feature:kubectl:Package", func() error { return packageFeature("kubectl") }) + gotaskr.Task("Feature:kubectl:Test", func() error { return testFeature("kubectl") }) + gotaskr.Task("Feature:kubectl:Publish", func() error { return publishFeature("kubectl") }) ////////// locale - gotaskr.Task("Feature:locale:Package", func() error { - return packageFeature("locale") - }) - gotaskr.Task("Feature:locale:Test", func() error { - return testFeature("locale") - }) - gotaskr.Task("Feature:locale:Publish", func() error { - return publishFeature("locale") - }) + gotaskr.Task("Feature:locale:Package", func() error { return packageFeature("locale") }) + gotaskr.Task("Feature:locale:Test", func() error { return testFeature("locale") }) + gotaskr.Task("Feature:locale:Publish", func() error { return publishFeature("locale") }) ////////// make - gotaskr.Task("Feature:make:Package", func() error { - return packageFeature("make") - }) - gotaskr.Task("Feature:make:Test", func() error { - return testFeature("make") - }) - gotaskr.Task("Feature:make:Publish", func() error { - return publishFeature("make") - }) + gotaskr.Task("Feature:make:Package", func() error { return packageFeature("make") }) + gotaskr.Task("Feature:make:Test", func() error { return testFeature("make") }) + gotaskr.Task("Feature:make:Publish", func() error { return publishFeature("make") }) ////////// mingw - gotaskr.Task("Feature:mingw:Package", func() error { - return packageFeature("mingw") - }) - gotaskr.Task("Feature:mingw:Test", func() error { - return testFeature("mingw") - }) - gotaskr.Task("Feature:mingw:Publish", func() error { - return publishFeature("mingw") - }) + gotaskr.Task("Feature:mingw:Package", func() error { return packageFeature("mingw") }) + gotaskr.Task("Feature:mingw:Test", func() error { return testFeature("mingw") }) + gotaskr.Task("Feature:mingw:Publish", func() error { return publishFeature("mingw") }) ////////// nginx - gotaskr.Task("Feature:nginx:Package", func() error { - return packageFeature("nginx") - }) - gotaskr.Task("Feature:nginx:Test", func() error { - return testFeature("nginx") - }) - gotaskr.Task("Feature:nginx:Publish", func() error { - return publishFeature("nginx") - }) + gotaskr.Task("Feature:nginx:Package", func() error { return packageFeature("nginx") }) + gotaskr.Task("Feature:nginx:Test", func() error { return testFeature("nginx") }) + gotaskr.Task("Feature:nginx:Publish", func() error { return publishFeature("nginx") }) ////////// node - gotaskr.Task("Feature:node:Package", func() error { - return packageFeature("node") - }) - gotaskr.Task("Feature:node:Test", func() error { - return testFeature("node") - }) - gotaskr.Task("Feature:node:Publish", func() error { - return publishFeature("node") - }) + gotaskr.Task("Feature:node:Package", func() error { return packageFeature("node") }) + gotaskr.Task("Feature:node:Test", func() error { return testFeature("node") }) + gotaskr.Task("Feature:node:Publish", func() error { return publishFeature("node") }) ////////// playwright-deps - gotaskr.Task("Feature:playwright-deps:Package", func() error { - return packageFeature("playwright-deps") - }) - gotaskr.Task("Feature:playwright-deps:Test", func() error { - return testFeature("playwright-deps") - }) - gotaskr.Task("Feature:playwright-deps:Publish", func() error { - return publishFeature("playwright-deps") - }) + gotaskr.Task("Feature:playwright-deps:Package", func() error { return packageFeature("playwright-deps") }) + gotaskr.Task("Feature:playwright-deps:Test", func() error { return testFeature("playwright-deps") }) + gotaskr.Task("Feature:playwright-deps:Publish", func() error { return publishFeature("playwright-deps") }) ////////// python - gotaskr.Task("Feature:python:Package", func() error { - return packageFeature("python") - }) - gotaskr.Task("Feature:python:Test", func() error { - return testFeature("python") - }) - gotaskr.Task("Feature:python:Publish", func() error { - return publishFeature("python") - }) + gotaskr.Task("Feature:python:Package", func() error { return packageFeature("python") }) + gotaskr.Task("Feature:python:Test", func() error { return testFeature("python") }) + gotaskr.Task("Feature:python:Publish", func() error { return publishFeature("python") }) ////////// sonar-scanner-cli - gotaskr.Task("Feature:sonar-scanner-cli:Package", func() error { - return packageFeature("sonar-scanner-cli") - }) - gotaskr.Task("Feature:sonar-scanner-cli:Test", func() error { - return testFeature("sonar-scanner-cli") - }) - gotaskr.Task("Feature:sonar-scanner-cli:Publish", func() error { - return publishFeature("sonar-scanner-cli") - }) + gotaskr.Task("Feature:sonar-scanner-cli:Package", func() error { return packageFeature("sonar-scanner-cli") }) + gotaskr.Task("Feature:sonar-scanner-cli:Test", func() error { return testFeature("sonar-scanner-cli") }) + gotaskr.Task("Feature:sonar-scanner-cli:Publish", func() error { 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") - }) + 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") - }) - gotaskr.Task("Feature:timezone:Test", func() error { - return testFeature("timezone") - }) - gotaskr.Task("Feature:timezone:Publish", func() error { - return publishFeature("timezone") - }) + gotaskr.Task("Feature:timezone:Package", func() error { return packageFeature("timezone") }) + gotaskr.Task("Feature:timezone:Test", func() error { return testFeature("timezone") }) + gotaskr.Task("Feature:timezone:Publish", func() error { return publishFeature("timezone") }) ////////// vault-cli - gotaskr.Task("Feature:vault-cli:Package", func() error { - return packageFeature("vault-cli") - }) - gotaskr.Task("Feature:vault-cli:Test", func() error { - return testFeature("vault-cli") - }) - gotaskr.Task("Feature:vault-cli:Publish", func() error { - return publishFeature("vault-cli") - }) + gotaskr.Task("Feature:vault-cli:Package", func() error { return packageFeature("vault-cli") }) + gotaskr.Task("Feature:vault-cli:Test", func() error { return testFeature("vault-cli") }) + gotaskr.Task("Feature:vault-cli:Publish", func() error { return publishFeature("vault-cli") }) ////////// zig - gotaskr.Task("Feature:zig:Package", func() error { - return packageFeature("zig") - }) - gotaskr.Task("Feature:zig:Test", func() error { - return testFeature("zig") - }) - gotaskr.Task("Feature:zig:Publish", func() error { - return publishFeature("zig") - }) + gotaskr.Task("Feature:zig:Package", func() error { return packageFeature("zig") }) + gotaskr.Task("Feature:zig:Test", func() error { return testFeature("zig") }) + gotaskr.Task("Feature:zig:Publish", func() error { return publishFeature("zig") }) } //////////////////////////////////////////////////////////// diff --git a/features/src/kubectl/README.md b/features/src/kubectl/README.md new file mode 100755 index 0000000..ea3a25a --- /dev/null +++ b/features/src/kubectl/README.md @@ -0,0 +1,49 @@ +# kubectl (kubectl) + +Installs kubectl and other tools for managing kubernetes. + +## Example Usage + +```json +"features": { + "ghcr.io/postfinance/devcontainer-features/kubectl:0.1.0": { + "version": "latest", + "kubectxVersion": "latest", + "kubensVersion": "latest", + "k9sVersion": "none", + "helmVersion": "none", + "kustomizeVersion": "none", + "kubeconformVersion": "none", + "kubescoreVersion": "none", + "downloadUrl": "", + "kubectxDownloadUrl": "", + "kubensDownloadUrl": "", + "k9sDownloadUrl": "", + "helmDownloadUrl": "", + "kustomizeDownloadUrl": "", + "kubeconformDownloadUrl": "", + "kubescoreDownloadUrl": "" + } +} +``` + +## Options + +| Option | Description | Type | Default Value | Proposals | +|-----|-----|-----|-----|-----| +| version | The version of kubectl to install. | string | latest | latest, none, 1.30.0 | +| kubectxVersion | The version of kubectx to install. | string | latest | latest, none, 0.9.5 | +| kubensVersion | The version of kubens to install. | string | latest | latest, none, 0.9.5 | +| k9sVersion | The version of k9s to install. | string | none | latest, none, 0.32.5 | +| helmVersion | The version of helm to install. | string | none | latest, none, 3.16.2 | +| kustomizeVersion | The version of kustomize to install. | string | none | latest, none, 5.4.2 | +| kubeconformVersion | The version of kubeconform to install. | string | none | latest, none, 0.6.6 | +| kubescoreVersion | The version of kube-score to install. | string | none | latest, none, 1.18.0 | +| downloadUrl | The download URL to use for kubectl binaries. | string | <empty> | https://mycompany.com/artifactory/dlk8sio-generic-remote/release | +| kubectxDownloadUrl | The download URL to use for kubectx binaries. | string | <empty> | | +| kubensDownloadUrl | The download URL to use for kubens binaries. | string | <empty> | | +| k9sDownloadUrl | The download URL to use for k9s binaries. | string | <empty> | | +| helmDownloadUrl | The download URL to use for helm binaries. | string | <empty> | https://mycompany.com/artifactory/gethelmsh-generic-remote | +| kustomizeDownloadUrl | The download URL to use for kustomize binaries. | string | <empty> | | +| kubeconformDownloadUrl | The download URL to use for kubeconform binaries. | string | <empty> | | +| kubescoreDownloadUrl | The download URL to use for kube-score binaries. | string | <empty> | | diff --git a/features/src/kubectl/devcontainer-feature.json b/features/src/kubectl/devcontainer-feature.json new file mode 100644 index 0000000..ca49402 --- /dev/null +++ b/features/src/kubectl/devcontainer-feature.json @@ -0,0 +1,134 @@ +{ + "id": "kubectl", + "version": "0.1.0", + "name": "kubectl", + "description": "Installs kubectl and other tools for managing kubernetes.", + "options": { + "version": { + "type": "string", + "proposals": [ + "latest", + "none", + "1.30.0" + ], + "default": "latest", + "description": "The version of kubectl to install." + }, + "kubectxVersion": { + "type": "string", + "proposals": [ + "latest", + "none", + "0.9.5" + ], + "default": "latest", + "description": "The version of kubectx to install." + }, + "kubensVersion": { + "type": "string", + "proposals": [ + "latest", + "none", + "0.9.5" + ], + "default": "latest", + "description": "The version of kubens to install." + }, + "k9sVersion": { + "type": "string", + "proposals": [ + "latest", + "none", + "0.32.5" + ], + "default": "none", + "description": "The version of k9s to install." + }, + "helmVersion": { + "type": "string", + "proposals": [ + "latest", + "none", + "3.16.2" + ], + "default": "none", + "description": "The version of helm to install." + }, + "kustomizeVersion": { + "type": "string", + "proposals": [ + "latest", + "none", + "5.4.2" + ], + "default": "none", + "description": "The version of kustomize to install." + }, + "kubeconformVersion": { + "type": "string", + "proposals": [ + "latest", + "none", + "0.6.6" + ], + "default": "none", + "description": "The version of kubeconform to install." + }, + "kubescoreVersion": { + "type": "string", + "proposals": [ + "latest", + "none", + "1.18.0" + ], + "default": "none", + "description": "The version of kube-score to install." + }, + "downloadUrl": { + "type": "string", + "default": "", + "proposals": [ + "https://mycompany.com/artifactory/dlk8sio-generic-remote/release" + ], + "description": "The download URL to use for kubectl binaries." + }, + "kubectxDownloadUrl": { + "type": "string", + "default": "", + "description": "The download URL to use for kubectx binaries." + }, + "kubensDownloadUrl": { + "type": "string", + "default": "", + "description": "The download URL to use for kubens binaries." + }, + "k9sDownloadUrl": { + "type": "string", + "default": "", + "description": "The download URL to use for k9s binaries." + }, + "helmDownloadUrl": { + "type": "string", + "default": "", + "proposals": [ + "https://mycompany.com/artifactory/gethelmsh-generic-remote" + ], + "description": "The download URL to use for helm binaries." + }, + "kustomizeDownloadUrl": { + "type": "string", + "default": "", + "description": "The download URL to use for kustomize binaries." + }, + "kubeconformDownloadUrl": { + "type": "string", + "default": "", + "description": "The download URL to use for kubeconform binaries." + }, + "kubescoreDownloadUrl": { + "type": "string", + "default": "", + "description": "The download URL to use for kube-score binaries." + } + } +} \ No newline at end of file diff --git a/features/src/kubectl/install.sh b/features/src/kubectl/install.sh new file mode 100755 index 0000000..edd812b --- /dev/null +++ b/features/src/kubectl/install.sh @@ -0,0 +1,19 @@ +. ./functions.sh + +"./installer_$(detect_arch)" \ + -version="${VERSION:-"latest"}" \ + -kubectxVersion="${KUBECTXVERSION:-"latest"}" \ + -kubensVersion="${KUBENSVERSION:-"latest"}" \ + -k9sVersion="${K9SVERSION:-"none"}" \ + -helmVersion="${HELMVERSION:-"none"}" \ + -kustomizeVersion="${KUSTOMIZEVERSION:-"none"}" \ + -kubeconformVersion="${KUBECONFORMVERSION:-"none"}" \ + -kubescoreVersion="${KUBESCOREVERSION:-"none"}" \ + -downloadUrl="${DOWNLOADURL:-""}" \ + -kubectxDownloadUrl="${KUBECTXDOWNLOADURL:-""}" \ + -kubensDownloadUrl="${KUBENSDOWNLOADURL:-""}" \ + -k9sDownloadUrl="${K9SDOWNLOADURL:-""}" \ + -helmDownloadUrl="${HELMDOWNLOADURL:-""}" \ + -kustomizeDownloadUrl="${KUSTOMIZEDOWNLOADURL:-""}" \ + -kubeconformDownloadUrl="${KUBECONFORMDOWNLOADURL:-""}" \ + -kubescoreDownloadUrl="${KUBESCOREDOWNLOADURL:-""}" diff --git a/features/src/kubectl/installer.go b/features/src/kubectl/installer.go new file mode 100644 index 0000000..77136da --- /dev/null +++ b/features/src/kubectl/installer.go @@ -0,0 +1,536 @@ +package main + +import ( + "builder/installer" + "flag" + "fmt" + "os" + "regexp" + + "github.com/roemer/goext" + "github.com/roemer/gover" + "github.com/samber/lo" +) + +////////// +// Configuration +////////// + +// Full Regex versioning, like v3.15.0-rc.2 +var semVerRegex *regexp.Regexp = regexp.MustCompile(`^v?(?P(\d+)\.(\d+)(?:\.(\d+))?(?:-([a-z]+)(?:\.?(\d+))?)?)$`) + +// Regex with 2-3 digits like v1.0 or v2.3.4 +var threeDigitRegex *regexp.Regexp = regexp.MustCompile(`^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", "") + kubectxVersion := flag.String("kubectxVersion", "latest", "") + kubensVersion := flag.String("kubensVersion", "latest", "") + k9sVersion := flag.String("k9sVersion", "none", "") + helmVersion := flag.String("helmVersion", "none", "") + kustomizeVersion := flag.String("kustomizeVersion", "none", "") + kubeconformVersion := flag.String("kubeconformVersion", "none", "") + kubescoreVersion := flag.String("kubescoreVersion", "none", "") + downloadUrl := flag.String("downloadUrl", "", "") + kubectxDownloadUrl := flag.String("kubectxDownloadUrl", "", "") + kubensDownloadUrl := flag.String("kubensDownloadUrl", "", "") + k9sDownloadUrl := flag.String("k9sDownloadUrl", "", "") + helmDownloadUrl := flag.String("helmDownloadUrl", "", "") + kustomizeDownloadUrl := flag.String("kustomizeDownloadUrl", "", "") + kubeconformDownloadUrl := flag.String("kubeconformDownloadUrl", "", "") + kubescoreDownloadUrl := flag.String("kubescoreDownloadUrl", "", "") + flag.Parse() + + // Load settings from an external file + if err := installer.LoadOverrides(); err != nil { + return err + } + + // Apply override logic for URLs + installer.HandleOverride(downloadUrl, "https://dl.k8s.io/release", "kubectl-download-url") + installer.HandleGitHubOverride(kubectxDownloadUrl, "ahmetb/kubectx", "kubectl-kubectx-download-url") + installer.HandleGitHubOverride(kubensDownloadUrl, "ahmetb/kubectx", "kubectl-kubens-download-url") // Yes, this is the kubectx repo + installer.HandleGitHubOverride(k9sDownloadUrl, "derailed/k9s", "kubectl-k9s-download-url") + installer.HandleOverride(helmDownloadUrl, "https://get.helm.sh", "kubectl-helm-download-url") + installer.HandleGitHubOverride(kustomizeDownloadUrl, "kubernetes-sigs/kustomize", "kubectl-kustomize-download-url") + installer.HandleGitHubOverride(kubeconformDownloadUrl, "yannh/kubeconform", "kubectl-kubeconform-download-url") + installer.HandleGitHubOverride(kubescoreDownloadUrl, "zegl/kube-score", "kubectl-kubescore-download-url") + + // Create and process the feature + feature := installer.NewFeature("kubectl", true, + &kubectlComponent{ + ComponentBase: installer.NewComponentBase("kubectl", *version), + DownloadUrl: *downloadUrl, + }, + &kubectxComponent{ + ComponentBase: installer.NewComponentBase("kubectx", *kubectxVersion), + DownloadUrl: *kubectxDownloadUrl, + }, + &kubensComponent{ + ComponentBase: installer.NewComponentBase("kubens", *kubensVersion), + DownloadUrl: *kubensDownloadUrl, + }, + &k9sComponent{ + ComponentBase: installer.NewComponentBase("k9s", *k9sVersion), + DownloadUrl: *k9sDownloadUrl, + }, + &helmComponent{ + ComponentBase: installer.NewComponentBase("helm", *helmVersion), + DownloadUrl: *helmDownloadUrl, + }, + &kustomizeComponent{ + ComponentBase: installer.NewComponentBase("kustomize", *kustomizeVersion), + DownloadUrl: *kustomizeDownloadUrl, + }, + &kubeconformComponent{ + ComponentBase: installer.NewComponentBase("kubeconform", *kubeconformVersion), + DownloadUrl: *kubeconformDownloadUrl, + }, + &kubescoreComponent{ + ComponentBase: installer.NewComponentBase("kubescore", *kubescoreVersion), + DownloadUrl: *kubescoreDownloadUrl, + }, + &fzfComponent{ + ComponentBase: installer.NewComponentBase("fzf", installer.VERSION_SYSTEM_DEFAULT), + }, + ) + return feature.Process() +} + +////////// +// kubectl +////////// + +type kubectlComponent struct { + *installer.ComponentBase + DownloadUrl string +} + +func (c *kubectlComponent) GetAllVersions() ([]*gover.Version, error) { + tags, err := installer.Tools.GitHub.GetTags("kubernetes", "kubernetes") + if err != nil { + return nil, err + } + return installer.Tools.Versioning.ParseVersionsFromList(tags, semVerRegex, true) +} + +func (c *kubectlComponent) InstallVersion(version *gover.Version) error { + // Download + archPart, err := installer.Tools.System.MapArchitecture(map[string]string{ + installer.AMD64: "amd64", + installer.ARM64: "arm64", + }) + if err != nil { + return err + } + downloadUrl := fmt.Sprintf("%s/v%s/bin/linux/%s/kubectl", c.DownloadUrl, version.Raw, archPart) + fileName := "kubectl" + if err := installer.Tools.Download.ToFile(downloadUrl, fileName, "kubectl"); err != nil { + return err + } + // Install + if err := goext.CmdRunners.Console.Run("install", "-m", "0755", fileName, "/usr/local/bin/kubectl"); err != nil { + return err + } + // Cleanup + if err := os.Remove(fileName); err != nil { + return err + } + return nil +} + +////////// +// kubectx +////////// + +type kubectxComponent struct { + *installer.ComponentBase + DownloadUrl string +} + +func (c *kubectxComponent) GetAllVersions() ([]*gover.Version, error) { + tags, err := installer.Tools.GitHub.GetTags("ahmetb", "kubectx") + if err != nil { + return nil, err + } + return installer.Tools.Versioning.ParseVersionsFromList(tags, threeDigitRegex, true) +} + +func (c *kubectxComponent) InstallVersion(version *gover.Version) error { + // Download + archPart, err := installer.Tools.System.MapArchitecture(map[string]string{ + installer.AMD64: "x86_64", + installer.ARM64: "arm64", + }) + if err != nil { + return err + } + fileName := fmt.Sprintf("kubectx_v%s_linux_%s.tar.gz", version.Raw, archPart) + 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, "kubectx"); err != nil { + return err + } + // Extract + if err := installer.Tools.Compression.ExtractTarGz(fileName, "kubectx", false); err != nil { + return err + } + // Install + if err := goext.CmdRunners.Console.Run("install", "-m", "0755", "kubectx/kubectx", "/usr/local/bin/kubectx"); err != nil { + return err + } + // Cleanup + if err := os.Remove(fileName); err != nil { + return err + } + if err := os.RemoveAll("kubectx"); err != nil { + return err + } + return nil +} + +////////// +// kubens +////////// + +type kubensComponent struct { + *installer.ComponentBase + DownloadUrl string +} + +func (c *kubensComponent) GetAllVersions() ([]*gover.Version, error) { + tags, err := installer.Tools.GitHub.GetTags("ahmetb", "kubectx") // Yes, this is the kubectx repo + if err != nil { + return nil, err + } + return installer.Tools.Versioning.ParseVersionsFromList(tags, threeDigitRegex, true) +} + +func (c *kubensComponent) InstallVersion(version *gover.Version) error { + // Download + archPart, err := installer.Tools.System.MapArchitecture(map[string]string{ + installer.AMD64: "x86_64", + installer.ARM64: "arm64", + }) + if err != nil { + return err + } + fileName := fmt.Sprintf("kubens_v%s_linux_%s.tar.gz", version.Raw, archPart) + 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, "kubens"); err != nil { + return err + } + // Extract + if err := installer.Tools.Compression.ExtractTarGz(fileName, "kubens", false); err != nil { + return err + } + // Install + if err := goext.CmdRunners.Console.Run("install", "-m", "0755", "kubens/kubens", "/usr/local/bin/kubens"); err != nil { + return err + } + // Cleanup + if err := os.Remove(fileName); err != nil { + return err + } + if err := os.RemoveAll("kubens"); err != nil { + return err + } + return nil +} + +////////// +// k9s +////////// + +type k9sComponent struct { + *installer.ComponentBase + DownloadUrl string +} + +func (c *k9sComponent) GetAllVersions() ([]*gover.Version, error) { + tags, err := installer.Tools.GitHub.GetTags("derailed", "k9s") + if err != nil { + return nil, err + } + return installer.Tools.Versioning.ParseVersionsFromList(tags, threeDigitRegex, true) +} + +func (c *k9sComponent) InstallVersion(version *gover.Version) error { + // Download + archPart, err := installer.Tools.System.MapArchitecture(map[string]string{ + installer.AMD64: "amd64", + installer.ARM64: "arm64", + }) + if err != nil { + return err + } + fileName := fmt.Sprintf("k9s_Linux_%s.tar.gz", archPart) + 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, "k9s"); err != nil { + return err + } + // Extract + if err := installer.Tools.Compression.ExtractTarGz(fileName, "k9s", false); err != nil { + return err + } + // Install + if err := goext.CmdRunners.Console.Run("install", "-m", "0755", "k9s/k9s", "/usr/local/bin/k9s"); err != nil { + return err + } + // Cleanup + if err := os.Remove(fileName); err != nil { + return err + } + if err := os.RemoveAll("k9s"); err != nil { + return err + } + return nil +} + +////////// +// helm +////////// + +type helmComponent struct { + *installer.ComponentBase + DownloadUrl string +} + +func (c *helmComponent) GetAllVersions() ([]*gover.Version, error) { + tags, err := installer.Tools.GitHub.GetTags("helm", "helm") + if err != nil { + return nil, err + } + return installer.Tools.Versioning.ParseVersionsFromList(tags, semVerRegex, true) +} + +func (c *helmComponent) InstallVersion(version *gover.Version) error { + // Download + archPart, err := installer.Tools.System.MapArchitecture(map[string]string{ + installer.AMD64: "amd64", + installer.ARM64: "arm64", + }) + if err != nil { + return err + } + fileName := fmt.Sprintf("helm-v%s-linux-%s.tar.gz", version.Raw, archPart) + downloadUrl, err := installer.Tools.Http.BuildUrl(c.DownloadUrl, fileName) + if err != nil { + return err + } + if err := installer.Tools.Download.ToFile(downloadUrl, fileName, "helm"); err != nil { + return err + } + // Extract + if err := installer.Tools.Compression.ExtractTarGz(fileName, "helm", false); err != nil { + return err + } + // Install + if err := goext.CmdRunners.Console.Run("install", "-m", "0755", fmt.Sprintf("helm/linux-%s/helm", archPart), "/usr/local/bin/helm"); err != nil { + return err + } + // Cleanup + if err := os.Remove(fileName); err != nil { + return err + } + if err := os.RemoveAll("helm"); err != nil { + return err + } + return nil +} + +////////// +// kustomize +////////// + +type kustomizeComponent struct { + *installer.ComponentBase + DownloadUrl string +} + +func (c *kustomizeComponent) GetAllVersions() ([]*gover.Version, error) { + tags, err := installer.Tools.GitHub.GetTags("kubernetes-sigs", "kustomize") + if err != nil { + return nil, err + } + filterRegex := regexp.MustCompile("kustomize/(.*)") + filteredTags := lo.FilterMap(tags, func(tag string, _ int) (string, bool) { + matches := filterRegex.FindStringSubmatch(tag) + if len(matches) > 1 { + return matches[1], true + } + return "", false + }) + return installer.Tools.Versioning.ParseVersionsFromList(filteredTags, threeDigitRegex, true) +} + +func (c *kustomizeComponent) InstallVersion(version *gover.Version) error { + // Download + archPart, err := installer.Tools.System.MapArchitecture(map[string]string{ + installer.AMD64: "amd64", + installer.ARM64: "arm64", + }) + if err != nil { + return err + } + fileName := fmt.Sprintf("kustomize_v%s_linux_%s.tar.gz", version.Raw, archPart) + downloadUrl, err := installer.Tools.Http.BuildUrl(c.DownloadUrl, "kustomize/v"+version.Raw, fileName) + if err != nil { + return err + } + if err := installer.Tools.Download.ToFile(downloadUrl, fileName, "kustomize"); err != nil { + return err + } + // Extract + if err := installer.Tools.Compression.ExtractTarGz(fileName, "kustomize", false); err != nil { + return err + } + // Install + if err := goext.CmdRunners.Console.Run("install", "-m", "0755", "kustomize/kustomize", "/usr/local/bin/kustomize"); err != nil { + return err + } + // Cleanup + if err := os.Remove(fileName); err != nil { + return err + } + if err := os.RemoveAll("kustomize"); err != nil { + return err + } + return nil +} + +////////// +// kubeconform +////////// + +type kubeconformComponent struct { + *installer.ComponentBase + DownloadUrl string +} + +func (c *kubeconformComponent) GetAllVersions() ([]*gover.Version, error) { + tags, err := installer.Tools.GitHub.GetTags("yannh", "kubeconform") + if err != nil { + return nil, err + } + return installer.Tools.Versioning.ParseVersionsFromList(tags, threeDigitRegex, true) +} + +func (c *kubeconformComponent) InstallVersion(version *gover.Version) error { + // Download + archPart, err := installer.Tools.System.MapArchitecture(map[string]string{ + installer.AMD64: "amd64", + installer.ARM64: "arm64", + }) + if err != nil { + return err + } + fileName := fmt.Sprintf("kubeconform-linux-%s.tar.gz", archPart) + 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, "kubeconform"); err != nil { + return err + } + // Extract + if err := installer.Tools.Compression.ExtractTarGz(fileName, "kubeconform", false); err != nil { + return err + } + // Install + if err := goext.CmdRunners.Console.Run("install", "-m", "0755", "kubeconform/kubeconform", "/usr/local/bin/kubeconform"); err != nil { + return err + } + // Cleanup + if err := os.Remove(fileName); err != nil { + return err + } + if err := os.RemoveAll("kubeconform"); err != nil { + return err + } + return nil +} + +////////// +// kubescore +////////// + +type kubescoreComponent struct { + *installer.ComponentBase + DownloadUrl string +} + +func (c *kubescoreComponent) GetAllVersions() ([]*gover.Version, error) { + tags, err := installer.Tools.GitHub.GetTags("zegl", "kube-score") + if err != nil { + return nil, err + } + return installer.Tools.Versioning.ParseVersionsFromList(tags, threeDigitRegex, true) +} + +func (c *kubescoreComponent) InstallVersion(version *gover.Version) error { + // Download + archPart, err := installer.Tools.System.MapArchitecture(map[string]string{ + installer.AMD64: "amd64", + installer.ARM64: "arm64", + }) + if err != nil { + return err + } + fileName := fmt.Sprintf("kube-score_%s_linux_%s.tar.gz", version.Raw, archPart) + 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, "kubescore"); err != nil { + return err + } + // Extract + if err := installer.Tools.Compression.ExtractTarGz(fileName, "kubescore", false); err != nil { + return err + } + // Install + if err := goext.CmdRunners.Console.Run("install", "-m", "0755", "kubescore/kube-score", "/usr/local/bin/kube-score"); err != nil { + return err + } + // Cleanup + if err := os.Remove(fileName); err != nil { + return err + } + if err := os.RemoveAll("kubescore"); err != nil { + return err + } + return nil +} + +////////// +// fzf +////////// + +type fzfComponent struct { + *installer.ComponentBase +} + +func (c *fzfComponent) InstallVersion(version *gover.Version) error { + return installer.Tools.System.InstallPackages("fzf") +} diff --git a/features/test/kubectl/full.sh b/features/test/kubectl/full.sh new file mode 100755 index 0000000..8307954 --- /dev/null +++ b/features/test/kubectl/full.sh @@ -0,0 +1,14 @@ +#!/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_version "$(kubectl version --client | head -1)" "Client Version: v1.31.2" +check_version "$(kubectx --version)" "0.9.5" +check_version "$(kubens --version)" "0.9.5" +check_version "$(k9s version --short | head -1)" "v0.32.5" +check_version "$(helm version --short)" "v3.16.2+g13654a5" +check_version "$(kustomize version)" "v5.4.2" +check_version "$(kubeconform -v)" "v0.6.6" +check_version "$(kube-score version)" "kube-score version: 1.18.0, commit: 0fb5f668e153c22696aa75ec769b080c41b5dd3d, built: 2024-02-05T14:13:15Z" diff --git a/features/test/kubectl/install.sh b/features/test/kubectl/install.sh new file mode 100755 index 0000000..4620fd4 --- /dev/null +++ b/features/test/kubectl/install.sh @@ -0,0 +1,7 @@ +#!/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_version "$(kubectl version --client | head -1)" "Client Version: v1.31.1" diff --git a/features/test/kubectl/scenarios.json b/features/test/kubectl/scenarios.json new file mode 100644 index 0000000..b1bc702 --- /dev/null +++ b/features/test/kubectl/scenarios.json @@ -0,0 +1,42 @@ +{ + "install": { + "build": { + "dockerfile": "Dockerfile", + "options": [ + "--add-host=host.docker.internal:host-gateway" + ] + }, + "features": { + "./kubectl": { + "version": "1.31.1", + "kubectxVersion": "none", + "kubensVersion": "none", + "k9sVersion": "none", + "helmVersion": "none", + "kustomizeVersion": "none", + "kubeconformVersion": "none", + "kubescoreVersion": "none" + } + } + }, + "full": { + "build": { + "dockerfile": "Dockerfile", + "options": [ + "--add-host=host.docker.internal:host-gateway" + ] + }, + "features": { + "./kubectl": { + "version": "1.31.2", + "kubectxVersion": "0.9.5", + "kubensVersion": "0.9.5", + "k9sVersion": "0.32.5", + "helmVersion": "3.16.2", + "kustomizeVersion": "5.4.2", + "kubeconformVersion": "0.6.6", + "kubescoreVersion": "1.18.0" + } + } + } +} \ No newline at end of file diff --git a/features/test/kubectl/test-images.json b/features/test/kubectl/test-images.json new file mode 100644 index 0000000..f4cf196 --- /dev/null +++ b/features/test/kubectl/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/go.mod b/go.mod index 7aa7314..b596dcd 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,17 @@ module builder -go 1.24.5 +go 1.24.6 + +toolchain go1.24.7 require ( github.com/roemer/goext v0.8.1 - github.com/roemer/gotaskr v0.6.0 + github.com/roemer/gotaskr v0.7.0 github.com/roemer/gover v0.8.0 - github.com/schollz/progressbar/v3 v3.18.0 - github.com/stretchr/testify v1.10.0 - github.com/ulikunitz/xz v0.5.12 + github.com/samber/lo v1.52.0 + github.com/schollz/progressbar/v3 v3.19.0 + github.com/stretchr/testify v1.11.1 + github.com/ulikunitz/xz v0.5.15 ) require ( @@ -17,7 +20,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/andybalholm/brotli v1.1.1 // indirect - github.com/cloudflare/circl v1.6.0 // indirect + github.com/cloudflare/circl v1.6.1 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect @@ -33,9 +36,9 @@ require ( github.com/gookit/color v1.5.4 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jfrog/archiver/v3 v3.6.1 // indirect - github.com/jfrog/build-info-go v1.10.12 // indirect + github.com/jfrog/build-info-go v1.10.17 // indirect github.com/jfrog/gofrog v1.7.6 // indirect - github.com/jfrog/jfrog-client-go v1.53.1 // indirect + github.com/jfrog/jfrog-client-go v1.54.7 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect @@ -60,6 +63,7 @@ require ( golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 0f1fc17..8b474e6 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oM github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY= -github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= -github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -60,12 +60,12 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= -github.com/jfrog/build-info-go v1.10.12 h1:KO/YUeKYtDrnpcmsXmwqr6akjzrwA0hSTUB+Op/HF88= -github.com/jfrog/build-info-go v1.10.12/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= +github.com/jfrog/build-info-go v1.10.17 h1:wnVd9KkyFGQgNL+oU1wXyJB7/Ui9O/MqUnNKUMsyoRw= +github.com/jfrog/build-info-go v1.10.17/go.mod h1:szdz9+WzB7+7PGnILLUgyY+OF5qD5geBT7UGNIxibyw= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= -github.com/jfrog/jfrog-client-go v1.53.1 h1:GDRLUDs6hhfGNjqbI+bjc3ApgBHnpVwURM+f26PVfyw= -github.com/jfrog/jfrog-client-go v1.53.1/go.mod h1:XxYs2QtlTm92yqJ5O4j4vzWI8d4sDtKQUT1miNHMgnw= +github.com/jfrog/jfrog-client-go v1.54.7 h1:S1geo9T5ZCAb7EkXSv+NJ0K8+yhDsxlrybHTosCilIg= +github.com/jfrog/jfrog-client-go v1.54.7/go.mod h1:cOy7Pn34bGtjp0eWHADTRJG5Er0qVnJIz04u+NGEpcQ= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -110,14 +110,16 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/roemer/goext v0.8.1 h1:947ssu/PoE/NfRogaOuYf/foTmlGuPOZJYrpY2R4SP0= github.com/roemer/goext v0.8.1/go.mod h1:D+HvjseqdlevkQ9QeCME6XvXoUfJwmlyRrY3YfmTnB8= -github.com/roemer/gotaskr v0.6.0 h1:TfQbMfiVGKsfXWOBf33h6gcc3bknhNnEip6YgLhLJ4w= -github.com/roemer/gotaskr v0.6.0/go.mod h1:ELbNvPC6EMI+gySWiAPK0wQgOkXlnGbYSzqHvWvTHh8= +github.com/roemer/gotaskr v0.7.0 h1:zP2SOVJAV4yGdItmEIYjYxgXF8gzmX+y8a6tnbXOz0o= +github.com/roemer/gotaskr v0.7.0/go.mod h1:ue5tvkLn9BG8i41JogicOxS5rYqJlbHW8Hst9VXxliA= github.com/roemer/gover v0.8.0 h1:DbzdgftIhofqW9M9zHSAmg5aZpabhuoM8H5NeQfM5Kk= github.com/roemer/gover v0.8.0/go.mod h1:mpNP8x0jTUfi6PuPHnGGLRf32Gh9HtOLa4b6O4jmT0I= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA= -github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc= +github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -126,13 +128,13 @@ github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slHPRKTTHT+XSHkepo= github.com/terminalstatic/go-xsd-validate v0.1.6/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= -github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= diff --git a/installer/github.go b/installer/github.go index 929035b..a9a8129 100644 --- a/installer/github.go +++ b/installer/github.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/http" + "os" "regexp" ) @@ -15,8 +16,18 @@ func (g *gitHub) GetTags(owner string, repo string) ([]string, error) { nextRegexp := regexp.MustCompile(`(?i)<([^<]*)>; rel="next"`) var tagNames []string for { + // Prepare the request + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + // Add the authorization header + apiToken := os.Getenv("DEV_FEATURE_TOKEN_GITHUB_API") + if apiToken != "" { + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", apiToken)) + } // Get the date for the current page - resp, err := http.Get(url) + resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } diff --git a/override-all.env b/override-all.env index 5147bb4..480df61 100644 --- a/override-all.env +++ b/override-all.env @@ -45,6 +45,16 @@ INSTANT_CLIENT_VERSIONS_URL="" JFROG_CLI_DOWNLOAD_URL="" JFROG_CLI_VERSIONS_URL="" +# kubectl +KUBECTL_DOWNLOAD_URL="" +KUBECTL_KUBECTX_DOWNLOAD_URL="" +KUBECTL_KUBENS_DOWNLOAD_URL="" +KUBECTL_K9S_DOWNLOAD_URL="" +KUBECTL_HELM_DOWNLOAD_URL="" +KUBECTL_KUSTOMIZE_DOWNLOAD_URL="" +KUBECTL_KUBECONFORM_DOWNLOAD_URL="" +KUBECTL_KUBESCORE_DOWNLOAD_URL="" + # nginx NGINX_DOWNLOAD_URL="" From eb91dced83202792c11571220b8d9514753e514f Mon Sep 17 00:00:00 2001 From: Roemer Date: Tue, 20 Jan 2026 14:45:37 +0000 Subject: [PATCH 2/2] chore: Simplified binary installation --- features/src/kubectl/installer.go | 17 ++++++++--------- features/src/vault-cli/installer.go | 5 +---- installer/system.go | 8 ++++++++ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/features/src/kubectl/installer.go b/features/src/kubectl/installer.go index 77136da..5f59cb0 100644 --- a/features/src/kubectl/installer.go +++ b/features/src/kubectl/installer.go @@ -7,7 +7,6 @@ import ( "os" "regexp" - "github.com/roemer/goext" "github.com/roemer/gover" "github.com/samber/lo" ) @@ -141,7 +140,7 @@ func (c *kubectlComponent) InstallVersion(version *gover.Version) error { return err } // Install - if err := goext.CmdRunners.Console.Run("install", "-m", "0755", fileName, "/usr/local/bin/kubectl"); err != nil { + if err := installer.Tools.System.InstallBinaryToUsrLocalBin(fileName, "kubectl"); err != nil { return err } // Cleanup @@ -190,7 +189,7 @@ func (c *kubectxComponent) InstallVersion(version *gover.Version) error { return err } // Install - if err := goext.CmdRunners.Console.Run("install", "-m", "0755", "kubectx/kubectx", "/usr/local/bin/kubectx"); err != nil { + if err := installer.Tools.System.InstallBinaryToUsrLocalBin("kubectx/kubectx", "kubectx"); err != nil { return err } // Cleanup @@ -242,7 +241,7 @@ func (c *kubensComponent) InstallVersion(version *gover.Version) error { return err } // Install - if err := goext.CmdRunners.Console.Run("install", "-m", "0755", "kubens/kubens", "/usr/local/bin/kubens"); err != nil { + if err := installer.Tools.System.InstallBinaryToUsrLocalBin("kubens/kubens", "kubens"); err != nil { return err } // Cleanup @@ -294,7 +293,7 @@ func (c *k9sComponent) InstallVersion(version *gover.Version) error { return err } // Install - if err := goext.CmdRunners.Console.Run("install", "-m", "0755", "k9s/k9s", "/usr/local/bin/k9s"); err != nil { + if err := installer.Tools.System.InstallBinaryToUsrLocalBin("k9s/k9s", "k9s"); err != nil { return err } // Cleanup @@ -346,7 +345,7 @@ func (c *helmComponent) InstallVersion(version *gover.Version) error { return err } // Install - if err := goext.CmdRunners.Console.Run("install", "-m", "0755", fmt.Sprintf("helm/linux-%s/helm", archPart), "/usr/local/bin/helm"); err != nil { + if err := installer.Tools.System.InstallBinaryToUsrLocalBin(fmt.Sprintf("helm/linux-%s/helm", archPart), "helm"); err != nil { return err } // Cleanup @@ -406,7 +405,7 @@ func (c *kustomizeComponent) InstallVersion(version *gover.Version) error { return err } // Install - if err := goext.CmdRunners.Console.Run("install", "-m", "0755", "kustomize/kustomize", "/usr/local/bin/kustomize"); err != nil { + if err := installer.Tools.System.InstallBinaryToUsrLocalBin("kustomize/kustomize", "kustomize"); err != nil { return err } // Cleanup @@ -458,7 +457,7 @@ func (c *kubeconformComponent) InstallVersion(version *gover.Version) error { return err } // Install - if err := goext.CmdRunners.Console.Run("install", "-m", "0755", "kubeconform/kubeconform", "/usr/local/bin/kubeconform"); err != nil { + if err := installer.Tools.System.InstallBinaryToUsrLocalBin("kubeconform/kubeconform", "kubeconform"); err != nil { return err } // Cleanup @@ -510,7 +509,7 @@ func (c *kubescoreComponent) InstallVersion(version *gover.Version) error { return err } // Install - if err := goext.CmdRunners.Console.Run("install", "-m", "0755", "kubescore/kube-score", "/usr/local/bin/kube-score"); err != nil { + if err := installer.Tools.System.InstallBinaryToUsrLocalBin("kubescore/kube-score", "kube-score"); err != nil { return err } // Cleanup diff --git a/features/src/vault-cli/installer.go b/features/src/vault-cli/installer.go index 8954222..c2c066d 100644 --- a/features/src/vault-cli/installer.go +++ b/features/src/vault-cli/installer.go @@ -6,10 +6,8 @@ import ( "flag" "fmt" "os" - "os/exec" "regexp" - "github.com/roemer/gotaskr/execr" "github.com/roemer/gover" ) @@ -110,8 +108,7 @@ func (c *vaultCliComponent) InstallVersion(version *gover.Version) error { return err } // Install - configureCmd := exec.Command("install", "-m", "0755", "vault/vault", "/usr/local/bin/vault") - if err := execr.RunCommand(true, configureCmd); err != nil { + if err := installer.Tools.System.InstallBinaryToUsrLocalBin("vault/vault", "vault"); err != nil { return err } // Cleanup diff --git a/installer/system.go b/installer/system.go index d749da8..86ec116 100644 --- a/installer/system.go +++ b/installer/system.go @@ -4,8 +4,11 @@ import ( "bufio" "fmt" "os" + "path/filepath" "runtime" "strings" + + "github.com/roemer/goext" ) const ( @@ -15,6 +18,11 @@ const ( type system struct{} +// Installs the given binary to /usr/local/bin with the given name. +func (s *system) InstallBinaryToUsrLocalBin(binaryPath string, binaryName string) error { + return goext.CmdRunners.Console.Run("install", "-m", "0755", binaryPath, filepath.Join("/usr/local/bin", binaryName)) +} + func (s *system) InstallPackages(packages ...string) error { osInfo, err := s.GetOsInfo() if err != nil {