├── .prettierignore ├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── .prettierrc.json ├── tsconfig.json ├── src ├── constants.ts └── main.ts ├── LICENSE ├── package.json ├── .gitignore ├── eslint.config.js ├── action.yml └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | node_modules/ -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": false, 9 | "arrowParens": "avoid", 10 | "parser": "typescript" 11 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "outDir": "./lib", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "noImplicitAny": true, 10 | "esModuleInterop": true 11 | }, 12 | "exclude": ["node_modules", "**/*.test.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const defaultProcessorArchType = 'amd64' 2 | 3 | export const defaultKubectlVersion = '1.34.1' 4 | export const defaultKustomizeVersion = '5.7.1' 5 | export const defaultHelmVersion = '3.19.0' 6 | export const defaultKubevalVersion = '0.16.1' 7 | export const defaultKubeconformVersion = '0.7.0' 8 | export const defaultConftestVersion = '0.62.0' 9 | export const defaultYqVersion = '4.47.2' 10 | export const defaultRancherVersion = '2.12.1' 11 | export const defaultTiltVersion = '0.35.1' 12 | export const defaultSkaffoldVersion = '2.16.1' 13 | export const defaultKubeScoreVersion = '1.20.0' 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2024 Yoichi Kawasaki and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "name": "action-setup-kube-tools", 4 | "version": "0.11.2", 5 | "private": true, 6 | "description": "Github Action that install Kubernetes tools (kubectl, kustomize, helm, kubeconform, conftest, yq, etc.) and cache them on the runner", 7 | "main": "lib/main.js", 8 | "scripts": { 9 | "build": "tsc", 10 | "format": "prettier --write **/*.ts", 11 | "format-check": "prettier --check **/*.ts", 12 | "lint": "eslint src/*.ts", 13 | "pack": "ncc build src/main.ts -o dist --target es2020 && mv dist/index.js dist/index.mjs", 14 | "all": "npm run build && npm run format && npm run lint && npm run pack" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/yokawasa/action-setup-kube-tools.git" 19 | }, 20 | "keywords": [ 21 | "actions", 22 | "node", 23 | "setup", 24 | "kubernetes", 25 | "kubectl", 26 | "kustomize", 27 | "helm", 28 | "kubeval", 29 | "kubeconform", 30 | "conftest", 31 | "yq", 32 | "rancher", 33 | "tilt", 34 | "skaffold", 35 | "kube-score" 36 | ], 37 | "author": "Yoichi Kawasaki", 38 | "license": "MIT", 39 | "dependencies": { 40 | "@actions/core": "^1.10.0", 41 | "@actions/exec": "^1.1.1", 42 | "@actions/tool-cache": "^2.0.1" 43 | }, 44 | "devDependencies": { 45 | "@eslint/js": "^9.35.0", 46 | "@types/node": "^24.3.1", 47 | "@typescript-eslint/eslint-plugin": "^8.43.0", 48 | "@typescript-eslint/parser": "^8.43.0", 49 | "@vercel/ncc": "^0.34.0", 50 | "eslint": "^9.35.0", 51 | "eslint-plugin-github": "^6.0.0", 52 | "eslint-plugin-jest": "^22.21.0", 53 | "js-yaml": "^3.13.1", 54 | "prettier": "^1.19.1", 55 | "ts-jest": "^26.1.0", 56 | "typescript": "^5.9.2" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | node_modules 3 | 4 | # Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # next.js build output 76 | .next 77 | 78 | # nuxt.js build output 79 | .nuxt 80 | 81 | # vuepress build output 82 | .vuepress/dist 83 | 84 | # Serverless directories 85 | .serverless/ 86 | 87 | # FuseBox cache 88 | .fusebox/ 89 | 90 | # DynamoDB Local files 91 | .dynamodb/ 92 | 93 | # OS metadata 94 | .DS_Store 95 | Thumbs.db 96 | 97 | # Ignore built ts files 98 | __tests__/runner/* 99 | lib/**/* 100 | # Ignore ncc generated files for resolving dependencies 101 | dist/package.json 102 | 103 | /t/* 104 | /*/t/* 105 | 106 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // ESLint v9+ config migrated from .eslintrc.json and .eslintignore 2 | import js from '@eslint/js'; 3 | import github from 'eslint-plugin-github'; 4 | import typescript from '@typescript-eslint/eslint-plugin'; 5 | import parser from '@typescript-eslint/parser'; 6 | 7 | /** @type {import('eslint').Linter.FlatConfig} */ 8 | export default [ 9 | js.configs.recommended, 10 | { 11 | plugins: { 12 | github, 13 | '@typescript-eslint': typescript, 14 | }, 15 | languageOptions: { 16 | parser, 17 | parserOptions: { 18 | ecmaVersion: 9, 19 | sourceType: 'module', 20 | project: './tsconfig.json', 21 | }, 22 | }, 23 | rules: { 24 | 'eslint-comments/no-use': 'off', 25 | 'import/no-namespace': 'off', 26 | 'no-unused-vars': 'off', 27 | '@typescript-eslint/no-unused-vars': 'error', 28 | '@typescript-eslint/explicit-member-accessibility': ['error', { accessibility: 'no-public' }], 29 | '@typescript-eslint/no-require-imports': 'error', 30 | '@typescript-eslint/array-type': 'error', 31 | '@typescript-eslint/await-thenable': 'error', 32 | '@typescript-eslint/ban-ts-ignore': 'error', 33 | 'camelcase': 'off', 34 | '@typescript-eslint/camelcase': 'error', 35 | '@typescript-eslint/class-name-casing': 'error', 36 | '@typescript-eslint/explicit-function-return-type': ['error', { allowExpressions: true }], 37 | '@typescript-eslint/func-call-spacing': ['error', 'never'], 38 | '@typescript-eslint/generic-type-naming': ['error', '^[A-Z][A-Za-z]*$'], 39 | '@typescript-eslint/no-array-constructor': 'error', 40 | '@typescript-eslint/no-empty-interface': 'error', 41 | '@typescript-eslint/no-explicit-any': 'error', 42 | '@typescript-eslint/no-extraneous-class': 'error', 43 | '@typescript-eslint/no-for-in-array': 'error', 44 | '@typescript-eslint/no-inferrable-types': 'error', 45 | '@typescript-eslint/no-misused-new': 'error', 46 | '@typescript-eslint/no-namespace': 'error', 47 | '@typescript-eslint/no-non-null-assertion': 'warn', 48 | '@typescript-eslint/no-object-literal-type-assertion': 'error', 49 | '@typescript-eslint/no-unnecessary-qualifier': 'error', 50 | '@typescript-eslint/no-unnecessary-type-assertion': 'error', 51 | '@typescript-eslint/no-useless-constructor': 'error', 52 | '@typescript-eslint/no-var-requires': 'error', 53 | '@typescript-eslint/prefer-for-of': 'warn', 54 | }, 55 | ignores: [ 56 | 'dist/', 57 | 'lib/', 58 | 'node_modules/', 59 | ], 60 | extends: [ 61 | 'plugin:github/es6', 62 | ], 63 | }, 64 | ]; 65 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Setup Kubernetes Tools' 2 | description: 'Setup Kubernetes tools: kubectl, kustomize, helm, kubeconform, conftest, yq, rancher, tilt, skaffold, kube-score' 3 | author: 'Yoichi Kawasaki @yokawasa' 4 | inputs: 5 | fail-fast: 6 | required: false 7 | default: 'true' 8 | description: 'the action immediately fails when it fails to download (ie. due to a bad version)' 9 | arch-type: 10 | required: false 11 | description: 'Optional. The processor architecture type of the tool binary to setup. The action will auto-detect the architecture ("amd64" or "arm64") and use it as appropriate at runtime. Specify the architecture type ("amd64" or "arm64") only if you need to force it.' 12 | setup-tools: 13 | required: false 14 | default: '' 15 | description: 'List of tool name to setup. By default, the action download and setup all supported Kubernetes tools. By specifying "setup-tools" you can choose which tools the action setup. Supported separator is return in multi-line string. Supported tools are "kubectl", "kustomize", "helm", "helmv3", "kubeval", "conftest", "yq", "rancher", "tilt", "skaffold", "kube-score"' 16 | kubectl: 17 | required: false 18 | default: '1.34.1' 19 | description: "kubectl version or 'latest'" 20 | kustomize: 21 | required: false 22 | default: '5.7.1' 23 | description: "kustomize version or 'latest'" 24 | helm: 25 | required: false 26 | default: '3.19.0' 27 | description: "helm version or 'latest'" 28 | kubeval: 29 | required: false 30 | default: '0.16.1' 31 | description: "kubeval version or 'latest'" 32 | kubeconform: 33 | required: false 34 | default: '0.7.0' 35 | description: "kubeconform version or 'latest'" 36 | conftest: 37 | required: false 38 | default: '0.62.0' 39 | description: "conftest version or 'latest'" 40 | yq: 41 | required: false 42 | default: '4.47.2' 43 | description: "yq version or 'latest'" 44 | rancher: 45 | required: false 46 | default: '2.12.1' 47 | description: "rancher cli version or 'latest'" 48 | tilt: 49 | required: false 50 | default: '0.35.1' 51 | description: "tilt version or 'latest'" 52 | skaffold: 53 | required: false 54 | default: '2.16.1' 55 | description: "skaffold version or 'latest'" 56 | kube-score: 57 | required: false 58 | default: '1.20.0' 59 | description: "kube-score version or 'latest'" 60 | outputs: 61 | kubectl-path: 62 | description: 'kubectl command path if the action setup the tool, otherwise empty string' 63 | kustomize-path: 64 | description: 'kustomize command path if the action setup the tool, otherwise empty string' 65 | helm-path: 66 | description: 'helm command path if the action setup the tool, otherwise empty string' 67 | kubeval-path: 68 | description: 'kubeval command path if the action setup the tool, otherwise empty string' 69 | conftest-path: 70 | description: 'conftest command path if the action setup the tool, otherwise empty string' 71 | yq-path: 72 | description: 'yq command path if the action setup the tool, otherwise empty string' 73 | rancher-path: 74 | description: 'rancher cli command path if the action setup the tool, otherwise empty string' 75 | tilt-path: 76 | description: 'tilt command path if the action setup the tool, otherwise empty string' 77 | skaffold-path: 78 | description: 'skaffold command path if the action setup the tool, otherwise empty string' 79 | kube-score-path: 80 | description: 'kube-score command path if the action setup the tool, otherwise empty string' 81 | branding: 82 | icon: 'terminal' 83 | color: 'blue' 84 | runs: 85 | using: 'node20' 86 | main: 'dist/index.mjs' 87 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "action-setup-kube-tools Test" 2 | on: 3 | # Schedule run: Runs at 00:00, only on Mondays (Japan Time) 4 | schedule: 5 | - cron: '0 15 * * 1' 6 | pull_request: 7 | push: 8 | branches: 9 | - master 10 | - 'releases/*' 11 | - 'v*' 12 | 13 | jobs: 14 | build: # make sure build/ci work properly 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v5 18 | - uses: actions/setup-node@v5 19 | with: 20 | node-version: '20.x' 21 | - run: | 22 | npm install 23 | npm run all 24 | test-all-tools-no-input: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v5 28 | - uses: ./ 29 | id: setup 30 | - run: | 31 | kubectl version --client 32 | kustomize version 33 | helm version 34 | kubeval --version 35 | kubeconform -v 36 | conftest --version 37 | yq --version 38 | rancher --version 39 | tilt version 40 | skaffold version 41 | kube-score version 42 | - run: | 43 | kubectl=${{steps.setup.outputs.kubectl-path}} 44 | kustomize=${{steps.setup.outputs.kustomize-path}} 45 | helm=${{steps.setup.outputs.helm-path}} 46 | kubeval=${{steps.setup.outputs.kubeval-path}} 47 | kubeconform=${{steps.setup.outputs.kubeconform-path}} 48 | conftest=${{steps.setup.outputs.conftest-path}} 49 | yq=${{steps.setup.outputs.yq-path}} 50 | rancher=${{steps.setup.outputs.rancher-path}} 51 | tilt=${{steps.setup.outputs.tilt-path}} 52 | skaffold=${{steps.setup.outputs.skaffold-path}} 53 | kubescore=${{steps.setup.outputs.kube-score-path}} 54 | 55 | ${kubectl} version --client 56 | ${kustomize} version 57 | ${helm} version 58 | ${kubeval} --version 59 | ${kubeconform} -v 60 | ${conftest} --version 61 | ${yq} --version 62 | ${rancher} --version 63 | ${tilt} version 64 | ${skaffold} version 65 | ${kubescore} version 66 | 67 | test-all-tools-with-versrion-input: 68 | runs-on: ubuntu-latest 69 | steps: 70 | - uses: actions/checkout@v5 71 | - uses: ./ 72 | with: 73 | kubectl: '1.17.1' 74 | kustomize: '3.7.0' 75 | helm: '3.2.4' 76 | kubeval: '0.16.1' 77 | kubeconform: '0.5.0' 78 | conftest: '0.18.2' 79 | yq: '4.7.1' 80 | rancher: '2.4.10' 81 | tilt: '0.18.11' 82 | skaffold: '1.20.0' 83 | kube-score: '1.10.1' 84 | id: setup 85 | - run: | 86 | kubectl version --client 87 | kustomize version 88 | helm version 89 | kubeval --version 90 | kubeconform -v 91 | conftest --version 92 | yq --version 93 | rancher --version 94 | tilt version 95 | skaffold version 96 | kube-score version 97 | - run: | 98 | kubectl=${{steps.setup.outputs.kubectl-path}} 99 | kustomize=${{steps.setup.outputs.kustomize-path}} 100 | helm=${{steps.setup.outputs.helm-path}} 101 | kubeval=${{steps.setup.outputs.kubeval-path}} 102 | kubeconform=${{steps.setup.outputs.kubeconform-path}} 103 | conftest=${{steps.setup.outputs.conftest-path}} 104 | yq=${{steps.setup.outputs.yq-path}} 105 | rancher=${{steps.setup.outputs.rancher-path}} 106 | tilt=${{steps.setup.outputs.tilt-path}} 107 | skaffold=${{steps.setup.outputs.skaffold-path}} 108 | kubescore=${{steps.setup.outputs.kube-score-path}} 109 | 110 | ${kubectl} version --client 111 | ${kustomize} version 112 | ${helm} version 113 | ${kubeval} --version 114 | ${kubeconform} -v 115 | ${conftest} --version 116 | ${yq} --version 117 | ${rancher} --version 118 | ${tilt} version 119 | ${skaffold} version 120 | ${kubescore} version 121 | 122 | test-with-some-tools-selected-and-latest: 123 | runs-on: ubuntu-latest 124 | steps: 125 | - uses: actions/checkout@v5 126 | - uses: ./ 127 | with: 128 | arch-type: 'amd64' 129 | setup-tools: | 130 | kubectl 131 | helm 132 | kustomize 133 | skaffold 134 | kubectl: latest 135 | helm: latest 136 | kustomize: latest 137 | skaffold: latest 138 | id: setup 139 | - run: | 140 | kubectl version --client 141 | kustomize version 142 | helm version 143 | skaffold version 144 | - run: | 145 | kubectl=${{steps.setup.outputs.kubectl-path}} 146 | kustomize=${{steps.setup.outputs.kustomize-path}} 147 | helm=${{steps.setup.outputs.helm-path}} 148 | kubeval=${{steps.setup.outputs.kubeval-path}} 149 | kubeconform=${{steps.setup.outputs.kubeconform-path}} 150 | conftest=${{steps.setup.outputs.conftest-path}} 151 | yq=${{steps.setup.outputs.yq-path}} 152 | rancher=${{steps.setup.outputs.rancher-path}} 153 | tilt=${{steps.setup.outputs.tilt-path}} 154 | skaffold=${{steps.setup.outputs.skaffold-path}} 155 | kubescore=${{steps.setup.outputs.kube-score-path}} 156 | 157 | if [ ! -z ${kubectl} ]; then 158 | ${kubectl} version --client 159 | fi 160 | if [ ! -z ${kustomize} ]; then 161 | ${kustomize} version 162 | fi 163 | if [ ! -z ${helm} ]; then 164 | ${helm} version 165 | fi 166 | if [ ! -z ${kubeval} ]; then 167 | ${kubeval} --version 168 | fi 169 | if [ ! -z ${kubeconform} ]; then 170 | ${kubeconform} -v 171 | fi 172 | if [ ! -z ${conftest} ]; then 173 | ${conftest} --version 174 | fi 175 | if [ ! -z ${yq} ]; then 176 | ${yq} --version 177 | fi 178 | if [ ! -z ${rancher} ]; then 179 | ${rancher} --version 180 | fi 181 | if [ ! -z ${tilt} ]; then 182 | ${tilt} version 183 | fi 184 | if [ ! -z ${skaffold} ]; then 185 | ${skaffold} version 186 | fi 187 | if [ ! -z ${kubescore} ]; then 188 | ${kubescore} version 189 | fi 190 | 191 | # ARM test (force ARM64 artifacts on x64 runner) 192 | test-force-arm64: 193 | runs-on: ubuntu-latest 194 | steps: 195 | - uses: actions/checkout@v5 196 | - uses: ./ 197 | with: 198 | arch-type: 'arm64' 199 | id: setup 200 | - run: | 201 | kubectl=${{steps.setup.outputs.kubectl-path}} 202 | kustomize=${{steps.setup.outputs.kustomize-path}} 203 | helm=${{steps.setup.outputs.helm-path}} 204 | kubeval=${{steps.setup.outputs.kubeval-path}} 205 | kubeconform=${{steps.setup.outputs.kubeconform-path}} 206 | conftest=${{steps.setup.outputs.conftest-path}} 207 | yq=${{steps.setup.outputs.yq-path}} 208 | rancher=${{steps.setup.outputs.rancher-path}} 209 | tilt=${{steps.setup.outputs.tilt-path}} 210 | skaffold=${{steps.setup.outputs.skaffold-path}} 211 | kubescore=${{steps.setup.outputs.kube-score-path}} 212 | 213 | echo "Expecting aarch64 ARM64 binaries (forced)" 214 | 215 | check_arch() { 216 | if [ -n "$1" ]; then 217 | file "$1" | tee /dev/stderr | grep -E "aarch64|ARM" >/dev/null 218 | fi 219 | } 220 | 221 | check_arch "${kubectl}" 222 | check_arch "${kustomize}" 223 | check_arch "${helm}" 224 | # kubeval may be empty on arm64 as unsupported 225 | check_arch "${kubeconform}" 226 | check_arch "${conftest}" 227 | check_arch "${yq}" 228 | check_arch "${rancher}" 229 | check_arch "${tilt}" 230 | check_arch "${skaffold}" 231 | check_arch "${kubescore}" 232 | 233 | # ARM64 auto-detect test (no arch-type input) 234 | test-arm-autodetect: 235 | runs-on: ubuntu-24.04-arm 236 | steps: 237 | - uses: actions/checkout@v5 238 | - name: Show runner arch 239 | run: | 240 | echo "runner.os=${{ runner.os }}" 241 | echo "runner.arch=${{ runner.arch }}" 242 | uname -a 243 | uname -m 244 | - uses: ./ 245 | id: setup 246 | - name: Verify tools execute on ARM64 247 | run: | 248 | kubectl version --client 249 | kustomize version 250 | helm version 251 | kubeconform -v 252 | conftest --version 253 | yq --version 254 | rancher --version 255 | tilt version 256 | skaffold version 257 | kube-score version 258 | - name: Verify binaries are ARM64 259 | run: | 260 | kubectl=${{steps.setup.outputs.kubectl-path}} 261 | kustomize=${{steps.setup.outputs.kustomize-path}} 262 | helm=${{steps.setup.outputs.helm-path}} 263 | kubeval=${{steps.setup.outputs.kubeval-path}} 264 | kubeconform=${{steps.setup.outputs.kubeconform-path}} 265 | conftest=${{steps.setup.outputs.conftest-path}} 266 | yq=${{steps.setup.outputs.yq-path}} 267 | rancher=${{steps.setup.outputs.rancher-path}} 268 | tilt=${{steps.setup.outputs.tilt-path}} 269 | skaffold=${{steps.setup.outputs.skaffold-path}} 270 | kubescore=${{steps.setup.outputs.kube-score-path}} 271 | 272 | echo "Expecting aarch64 ARM64 binaries on ARM runner" 273 | 274 | check_arch() { 275 | if [ -n "$1" ]; then 276 | file "$1" | tee /dev/stderr | grep -E "aarch64|ARM" >/dev/null 277 | fi 278 | } 279 | 280 | check_arch "${kubectl}" 281 | check_arch "${kustomize}" 282 | check_arch "${helm}" 283 | # kubeval may be skipped on arm64 (no support) 284 | check_arch "${kubeconform}" 285 | check_arch "${conftest}" 286 | check_arch "${yq}" 287 | check_arch "${rancher}" 288 | check_arch "${tilt}" 289 | check_arch "${skaffold}" 290 | check_arch "${kubescore}" 291 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![action-setup-kube-tools Test](https://github.com/yokawasa/action-setup-kube-tools/actions/workflows/test.yml/badge.svg)](https://github.com/yokawasa/action-setup-kube-tools/actions/workflows/test.yml) 2 | 3 | # action-setup-kube-tools 4 | A GitHub Action that setup Kubernetes tools (kubectl, kustomize, helm, kubeconform, conftest, yq, rancher, tilt, skaffold, kube-score) and cache them on the runner. It is like a typescript version of [stefanprodan/kube-tools](https://github.com/stefanprodan/kube-tools) with no command input param, but as compared with [it](https://github.com/stefanprodan/kube-tools), it's **very fast** as it installs the tools asynchronously. 5 | 6 | ## Usage 7 | 8 | ### Inputs 9 | 10 | |Parameter|Required|Default Value|Description| 11 | |:--:|:--:|:--:|:--| 12 | |`fail-fast`|`false`|`true`| the action immediately fails when it fails to download (ie. due to a bad version) | 13 | |`arch-type`|`false`|`""`| Optional. The processor architecture type of the tool binary to setup. The action will auto-detect the architecture (`amd64` or `arm64`) and use it as appropriate at runtime. Specify the architecture type (`amd64` or `arm64`) only if you need to force it.| 14 | |`setup-tools`|`false`|`""`|List of tool name to setup. By default, the action download and setup all supported Kubernetes tools. By specifying `setup-tools` you can choose which tools the action setup. Supported separator is `return` in multi-line string. Supported tools are `kubectl`, `kustomize`, `helm`, `helmv3`, `kubeval`, `conftest`, `yq`, `rancher`, `tilt`, `skaffold`, `kube-score`| 15 | |`kubectl`|`false`|`1.34.1`| kubectl version or `latest`. kubectl versions can be found [here](https://github.com/kubernetes/kubernetes/releases)| 16 | |`kustomize`|`false`|`5.7.1`| kustomize version or `latest`. kustomize versions can be found [here](https://github.com/kubernetes-sigs/kustomize/releases)| 17 | |`helm`|`false`|`3.19.0`| helm version or `latest`. helm versions can be found [here](https://github.com/helm/helm/releases)| 18 | |`kubeval`|`false`|`0.16.1`| kubeval version (must be **0.16.1+**) or `latest`. kubeval versions can be found [here](https://github.com/instrumenta/kubeval/releases).
NOTE: this parameter is deprecating as `kubeval` is no longer maintained. A good replacement is [kubeconform](https://github.com/yannh/kubeconform). See also [this](https://github.com/instrumenta/kubeval) for more details.| 19 | |`kubeconform`|`false`|`0.7.0`| kubeconform version or `latest`. kubeconform versions can be found [here](https://github.com/yannh/kubeconform/releases)| 20 | |`conftest`|`false`|`0.62.0`| conftest version or `latest`. conftest versions can be found [here](https://github.com/open-policy-agent/conftest/releases)| 21 | |`yq`|`false`|`4.47.2`| yq version or `latest`. yq versions can be found [here](https://github.com/mikefarah/yq/releases/)| 22 | |`rancher`|`false`|`2.12.1`| Rancher CLI version or `latest`. Rancher CLI versions can be found [here](https://github.com/rancher/cli/releases)| 23 | |`tilt`|`false`|`0.35.1`| Tilt version or `latest`. Tilt versions can be found [here](https://github.com/tilt-dev/tilt/releases)| 24 | |`skaffold`|`false`|`2.16.1`| Skaffold version or `latest`. Skaffold versions can be found [here](https://github.com/GoogleContainerTools/skaffold/releases)| 25 | |`kube-score`|`false`|`1.20.0`| kube-score version or `latest`. kube-score versions can be found [here](https://github.com/zegl/kube-score/releases)| 26 | 27 | > [!NOTE] 28 | > - Supported Environments: `Linux` 29 | > - From `v0.7.0`, the action supports tool version 'v' prefix. Prior to v0.7.0, the action only accept the tool version without 'v' prefix but from v0.7.0 the action automatically add/remove the prefix as necessary 30 | > - From `v0.13.X`, the action supports `latest` as tool version. If a tool input is set to `latest`, the action resolves the latest version at runtime, then downloads and caches that exact version. However, using `latest` can make builds non-reproducible, as the installed version may change over time. For stable and repeatable builds, **it is recommended to specify exact versions** 31 | 32 | ### Outputs 33 | |Parameter|Description| 34 | |:--:|:--| 35 | |`kubectl-path`| kubectl command path if the action setup the tool, otherwise empty string | 36 | |`kustomize-path`| kustomize command path if the action setup the tool, otherwise empty string | 37 | |`helm-path`| helm command path if the action setup the tool, otherwise empty string | 38 | |`kubeval-path`| kubeval command path if the action setup the tool, otherwise empty string | 39 | |`kubeconform-path`| kubeconform command path if the action setup the tool, otherwise empty string | 40 | |`conftest-path`| conftest command path if the action setup the tool, otherwise empty string | 41 | |`yq-path`| yq command path if the action setup the tool, otherwise empty string | 42 | |`rancher-path`| rancher command path if the action setup the tool, otherwise empty string | 43 | |`tilt-path`| rancher command path if the action setup the tool, otherwise empty string | 44 | |`skaffold-path`| rancher command path if the action setup the tool, otherwise empty string | 45 | |`kube-score-path:`| rancher command path if the action setup the tool, otherwise empty string | 46 | 47 | ### Sample Workflow 48 | 49 | Pinned versions (reproducible): 50 | 51 | ```yaml 52 | test: 53 | runs-on: ubuntu-latest 54 | steps: 55 | - uses: actions/checkout@v4 56 | - uses: yokawasa/action-setup-kube-tools@v0.13.1 57 | with: 58 | kubectl: '1.34.1' 59 | kustomize: '5.7.1' 60 | helm: '3.19.0' 61 | kubeconform: '0.7.0' 62 | conftest: '0.62.0' 63 | rancher: '2.12.1' 64 | tilt: '0.35.2' 65 | skaffold: '2.16.1' 66 | kube-score: '1.20.0' 67 | - run: | 68 | kubectl version --client 69 | kustomize version 70 | helm version 71 | kubeconform -v 72 | conftest --version 73 | yq --version 74 | rancher --version 75 | tilt version 76 | skaffold version 77 | kube-score version 78 | ``` 79 | 80 | Default versions for the commands will be setup if you don't give any inputs like this: 81 | 82 | ```yaml 83 | test: 84 | runs-on: ubuntu-latest 85 | steps: 86 | - uses: actions/checkout@v4 87 | - uses: yokawasa/action-setup-kube-tools@v0.13.1 88 | - run: | 89 | kubectl version --client 90 | kustomize version 91 | helm version 92 | kubeconform -v 93 | conftest --version 94 | yq --version 95 | rancher --version 96 | tilt version 97 | skaffold version 98 | kube-score version 99 | ``` 100 | 101 | By specifying setup-tools you can choose which tools the action setup. Supported separator is return in multi-line string like this 102 | 103 | ```yaml 104 | test: 105 | runs-on: ubuntu-latest 106 | steps: 107 | - uses: actions/checkout@v4 108 | - uses: yokawasa/action-setup-kube-tools@v0.13.1 109 | with: 110 | setup-tools: | 111 | kubectl 112 | helm 113 | kustomize 114 | skaffold 115 | kubectl: '1.25' 116 | helm: '3.11.1' 117 | kustomize: '5.0.0' 118 | skaffold: '2.1.0' 119 | - run: | 120 | kubectl version --client 121 | kustomize version 122 | helm version 123 | skaffold version 124 | ``` 125 | 126 | Architecture is automatically detected on the runner (amd64 or arm64). You can optionally force it by specifying `arch-type: 'amd64'` or `arch-type: 'arm64'`. 127 | 128 | ```yaml 129 | test: 130 | steps: 131 | - uses: actions/checkout@v4 132 | - uses: yokawasa/action-setup-kube-tools@v0.13.1 133 | with: 134 | # arch-type is optional; uncomment to force arm64 135 | # arch-type: 'arm64' 136 | setup-tools: | 137 | kubectl 138 | helm 139 | kustomize 140 | skaffold 141 | kubectl: '1.25' 142 | helm: '3.11.1' 143 | kustomize: '5.0.0' 144 | skaffold: '2.1.0' 145 | - run: | 146 | kubectl version --client 147 | kustomize version 148 | helm version 149 | skaffold version 150 | ``` 151 | 152 | Explicit latest inputs (optional): 153 | 154 | ```yaml 155 | test: 156 | steps: 157 | - uses: actions/checkout@v4 158 | - uses: yokawasa/action-setup-kube-tools@v0.13.1 159 | with: 160 | # arch-type is optional; uncomment to force arm64 161 | # arch-type: 'arm64' 162 | setup-tools: | 163 | kubectl 164 | helm 165 | kustomize 166 | skaffold 167 | kubectl: latest 168 | helm: latest 169 | kustomize: latest 170 | skaffold: latest 171 | - run: | 172 | kubectl version --client 173 | kustomize version 174 | helm version 175 | skaffold version 176 | ``` 177 | 178 | Note: Using `latest` makes builds non-reproducible since versions can change over time. Prefer pinning exact versions for stability. 179 | 180 | 181 | ## Developing the action 182 | 183 | Install the dependencies 184 | ```bash 185 | npm install 186 | ``` 187 | 188 | Build the typescript and package it for distribution by running [ncc](https://github.com/zeit/ncc) 189 | ```bash 190 | npm run build && npm run format && npm run lint && npm run pack 191 | ``` 192 | 193 | Finally push the results 194 | ``` 195 | git add dist 196 | git commit -a -m "prod dependencies" 197 | git push origin releases/v0.13.X 198 | ``` 199 | 200 | ## References 201 | 202 | - https://kubernetes.io/releases/ 203 | - https://github.com/kubernetes-sigs/kustomize/releases 204 | - https://github.com/helm/helm/releases 205 | - https://helm.sh/docs/topics/version_skew/ 206 | - https://github.com/instrumenta/kubeval/releases 207 | - https://github.com/open-policy-agent/conftest/releases 208 | - https://github.com/mikefarah/yq/releases 209 | - https://github.com/rancher/cli/releases 210 | - https://github.com/tilt-dev/tilt/releases 211 | - https://github.com/GoogleContainerTools/skaffold/releases 212 | - https://github.com/zegl/kube-score/releases 213 | 214 | ## Contributing 215 | Bug reports and pull requests are welcome on GitHub at https://github.com/yokawasa/action-setup-kube-tools 216 | 217 | ## Changelog 218 | 219 | Please see the [list of releases](https://github.com/yokawasa/action-setup-kube-tools/releases) for information on changes between releases. 220 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as os from 'os' 2 | import * as path from 'path' 3 | import * as util from 'util' 4 | import * as fs from 'fs' 5 | import * as https from 'https' 6 | 7 | import * as toolCache from '@actions/tool-cache' 8 | import * as core from '@actions/core' 9 | 10 | import { 11 | defaultProcessorArchType, 12 | defaultKubectlVersion, 13 | defaultKustomizeVersion, 14 | defaultHelmVersion, 15 | defaultKubevalVersion, 16 | defaultKubeconformVersion, 17 | defaultConftestVersion, 18 | defaultYqVersion, 19 | defaultRancherVersion, 20 | defaultTiltVersion, 21 | defaultSkaffoldVersion, 22 | defaultKubeScoreVersion 23 | } from './constants' 24 | 25 | // Determine the processor architecture type based on the current runtime. 26 | // Maps Node's os.arch() to the values used by download URLs: 'amd64' | 'arm64' 27 | function detectArchType(): string { 28 | const nodeArch = os.arch().toLowerCase() 29 | if (nodeArch === 'arm64' || nodeArch === 'aarch64') { 30 | return 'arm64' 31 | } 32 | return defaultProcessorArchType 33 | } 34 | interface Tool { 35 | name: string 36 | defaultVersion: string 37 | isArchived: boolean 38 | supportArm: boolean 39 | commandPathInPackage: string 40 | } 41 | 42 | const Tools: Tool[] = [ 43 | { 44 | name: 'kubectl', 45 | defaultVersion: defaultKubectlVersion, 46 | isArchived: false, 47 | supportArm: true, 48 | commandPathInPackage: 'kubectl' 49 | }, 50 | { 51 | name: 'kustomize', 52 | defaultVersion: defaultKustomizeVersion, 53 | isArchived: true, 54 | supportArm: true, 55 | commandPathInPackage: 'kustomize' 56 | }, 57 | { 58 | name: 'helm', 59 | defaultVersion: defaultHelmVersion, 60 | isArchived: true, 61 | supportArm: true, 62 | commandPathInPackage: 'linux-{arch}/helm' 63 | }, 64 | { 65 | name: 'kubeval', 66 | defaultVersion: defaultKubevalVersion, 67 | isArchived: true, 68 | supportArm: false, 69 | commandPathInPackage: 'kubeval' 70 | }, 71 | { 72 | name: 'kubeconform', 73 | defaultVersion: defaultKubeconformVersion, 74 | isArchived: true, 75 | supportArm: true, 76 | commandPathInPackage: 'kubeconform' 77 | }, 78 | { 79 | name: 'conftest', 80 | defaultVersion: defaultConftestVersion, 81 | isArchived: true, 82 | supportArm: true, 83 | commandPathInPackage: 'conftest' 84 | }, 85 | { 86 | name: 'yq', 87 | defaultVersion: defaultYqVersion, 88 | isArchived: false, 89 | supportArm: true, 90 | commandPathInPackage: 'yq_linux_{arch}' 91 | }, 92 | { 93 | name: 'rancher', 94 | defaultVersion: defaultRancherVersion, 95 | isArchived: true, 96 | supportArm: true, 97 | commandPathInPackage: 'rancher-v{ver}/rancher' 98 | }, 99 | { 100 | name: 'tilt', 101 | defaultVersion: defaultTiltVersion, 102 | isArchived: true, 103 | supportArm: true, 104 | commandPathInPackage: 'tilt' 105 | }, 106 | { 107 | name: 'skaffold', 108 | defaultVersion: defaultSkaffoldVersion, 109 | isArchived: false, 110 | supportArm: true, 111 | commandPathInPackage: 'skaffold-linux-{arch}' 112 | }, 113 | { 114 | name: 'kube-score', 115 | defaultVersion: defaultKubeScoreVersion, 116 | isArchived: false, 117 | supportArm: true, 118 | commandPathInPackage: 'kube-score' 119 | } 120 | ] 121 | 122 | // Replace all {ver} and {arch} placeholders in the source format string with the actual values 123 | function replacePlaceholders( 124 | format: string, 125 | version: string, 126 | archType: string 127 | ): string { 128 | return format.replace(/{ver}|{arch}/g, match => { 129 | return match === '{ver}' ? version : archType 130 | }) 131 | } 132 | 133 | // Perform a simple HTTPS GET and return the response body as string 134 | async function httpGet(url: string): Promise { 135 | return new Promise((resolve, reject) => { 136 | const req = https.get( 137 | url, 138 | { 139 | headers: { 140 | 'User-Agent': 'yokawasa/action-setup-kube-tools', 141 | Accept: 'application/vnd.github+json' 142 | } 143 | }, 144 | res => { 145 | if (!res.statusCode) { 146 | reject(new Error(`Request failed: ${url}`)) 147 | return 148 | } 149 | if ( 150 | res.statusCode >= 300 && 151 | res.statusCode < 400 && 152 | res.headers.location 153 | ) { 154 | //// SSRF attach protection (Disabled for now to avoid many allowed domain changes) 155 | // Validate redirect location domain to avoid SSRF attack before following it. 156 | // Need to add domain names to allow as needed in the future 157 | // try { 158 | // const allowedDomains = [ 159 | // 'github.com', 160 | // 'api.github.com', 161 | // 'raw.githubusercontent.com', 162 | // 'dl.k8s.io', 163 | // 'cdn.dl.k8s.io', 164 | // 'get.helm.sh', 165 | // 'storage.googleapis.com' 166 | // ] 167 | // const redirectUrl = new URL(res.headers.location, url) 168 | // if (!allowedDomains.includes(redirectUrl.hostname)) { 169 | // reject( 170 | // new Error( 171 | // `Redirect to disallowed domain: ${redirectUrl.hostname}` 172 | // ) 173 | // ) 174 | // return 175 | // } 176 | // httpGet(redirectUrl.toString()) 177 | // .then(resolve) 178 | // .catch(reject) 179 | // } catch (e) { 180 | // reject(new Error(`Invalid redirect URL: ${res.headers.location}`)) 181 | // } 182 | 183 | //// Follow the redirect 184 | httpGet(res.headers.location) 185 | .then(resolve) 186 | .catch(reject) 187 | return 188 | } 189 | if (res.statusCode < 200 || res.statusCode >= 300) { 190 | reject(new Error(`Request failed: ${url} (status ${res.statusCode})`)) 191 | return 192 | } 193 | let data = '' 194 | res.on('data', chunk => (data += chunk)) 195 | res.on('end', () => resolve(data)) 196 | } 197 | ) 198 | req.on('error', reject) 199 | }) 200 | } 201 | 202 | // Normalize tag to a bare version (strip prefixes like 'v' or 'kustomize/v') 203 | function normalizeTagToVersion(tag: string, toolName: string): string { 204 | let t = tag.trim() 205 | if (toolName === 'kustomize') { 206 | if (t.startsWith('kustomize/')) { 207 | t = t.substring('kustomize/'.length) 208 | } 209 | } 210 | if (t.startsWith('v') || t.startsWith('V')) { 211 | t = t.substring(1) 212 | } 213 | return t 214 | } 215 | 216 | async function getLatestVersion(toolName: string): Promise { 217 | try { 218 | if (toolName === 'kubectl') { 219 | const body = await httpGet('https://dl.k8s.io/release/stable.txt') 220 | return normalizeTagToVersion(body, toolName) 221 | } 222 | 223 | const repoMap: {[key: string]: string} = { 224 | kustomize: 'kubernetes-sigs/kustomize', 225 | helm: 'helm/helm', 226 | kubeval: 'instrumenta/kubeval', 227 | kubeconform: 'yannh/kubeconform', 228 | conftest: 'open-policy-agent/conftest', 229 | yq: 'mikefarah/yq', 230 | rancher: 'rancher/cli', 231 | tilt: 'tilt-dev/tilt', 232 | skaffold: 'GoogleContainerTools/skaffold', 233 | 'kube-score': 'zegl/kube-score' 234 | } 235 | const repo = repoMap[toolName] 236 | if (!repo) { 237 | throw new Error(`Unsupported tool for latest lookup: ${toolName}`) 238 | } 239 | const api = `https://api.github.com/repos/${repo}/releases/latest` 240 | const json = await httpGet(api) 241 | let meta 242 | try { 243 | meta = JSON.parse(json) 244 | } catch (e) { 245 | // Truncate the response for safety if it's too long: #75 246 | const truncatedJson = 247 | json && json.length > 500 248 | ? json.substring(0, 500) + '...[truncated]' 249 | : json 250 | throw new Error( 251 | `Failed to parse JSON response from ${api} for ${toolName}: ${e}. Response: ${truncatedJson}` 252 | ) 253 | } 254 | if (!meta || !meta.tag_name) { 255 | throw new Error(`Unexpected response resolving latest for ${toolName}`) 256 | } 257 | return normalizeTagToVersion(String(meta.tag_name), toolName) 258 | } catch (e) { 259 | throw new Error(`Failed to resolve latest version for ${toolName}: ${e}`) 260 | } 261 | } 262 | 263 | function getDownloadURL( 264 | commandName: string, 265 | version: string, 266 | archType: string 267 | ): string { 268 | let actualArchType = archType 269 | let urlFormat = '' 270 | switch (commandName) { 271 | case 'kubectl': 272 | urlFormat = 'https://dl.k8s.io/release/v{ver}/bin/linux/{arch}/kubectl' 273 | break 274 | case 'kustomize': 275 | urlFormat = 276 | 'https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv{ver}/kustomize_v{ver}_linux_{arch}.tar.gz' 277 | break 278 | case 'helm': 279 | urlFormat = 'https://get.helm.sh/helm-v{ver}-linux-{arch}.tar.gz' 280 | break 281 | case 'kubeval': 282 | actualArchType = 'amd64' // kubeval only supports amd64 283 | urlFormat = 284 | 'https://github.com/instrumenta/kubeval/releases/download/v{ver}/kubeval-linux-{arch}.tar.gz' 285 | break 286 | case 'kubeconform': 287 | urlFormat = 288 | 'https://github.com/yannh/kubeconform/releases/download/v{ver}/kubeconform-linux-{arch}.tar.gz' 289 | break 290 | case 'conftest': 291 | actualArchType = archType === 'arm64' ? archType : 'x86_64' 292 | urlFormat = 293 | 'https://github.com/open-policy-agent/conftest/releases/download/v{ver}/conftest_{ver}_Linux_{arch}.tar.gz' 294 | break 295 | case 'yq': 296 | urlFormat = 297 | 'https://github.com/mikefarah/yq/releases/download/v{ver}/yq_linux_{arch}' 298 | break 299 | case 'rancher': 300 | actualArchType = archType === 'arm64' ? 'arm' : archType 301 | urlFormat = 302 | 'https://github.com/rancher/cli/releases/download/v{ver}/rancher-linux-{arch}-v{ver}.tar.gz' 303 | break 304 | case 'tilt': 305 | actualArchType = archType === 'arm64' ? archType : 'x86_64' 306 | urlFormat = 307 | 'https://github.com/tilt-dev/tilt/releases/download/v{ver}/tilt.{ver}.linux.{arch}.tar.gz' 308 | break 309 | case 'skaffold': 310 | urlFormat = 311 | 'https://github.com/GoogleContainerTools/skaffold/releases/download/v{ver}/skaffold-linux-{arch}' 312 | break 313 | case 'kube-score': 314 | urlFormat = 315 | 'https://github.com/zegl/kube-score/releases/download/v{ver}/kube-score_{ver}_linux_{arch}' 316 | break 317 | default: 318 | return '' 319 | } 320 | return replacePlaceholders(urlFormat, version, actualArchType) 321 | } 322 | 323 | async function downloadTool( 324 | version: string, 325 | archType: string, 326 | tool: Tool 327 | ): Promise { 328 | let cachedToolPath = toolCache.find(tool.name, version) 329 | let commandPathInPackage = tool.commandPathInPackage 330 | let commandPath = '' 331 | 332 | if (!cachedToolPath) { 333 | const downloadURL = getDownloadURL(tool.name, version, archType) 334 | 335 | try { 336 | const packagePath = await toolCache.downloadTool(downloadURL) 337 | 338 | if (tool.isArchived) { 339 | const extractTarBaseDirPath = util.format( 340 | '%s_%s', 341 | packagePath, 342 | tool.name 343 | ) 344 | 345 | fs.mkdirSync(extractTarBaseDirPath) 346 | 347 | const extractedDirPath = await toolCache.extractTar( 348 | packagePath, 349 | extractTarBaseDirPath 350 | ) 351 | 352 | commandPathInPackage = replacePlaceholders( 353 | commandPathInPackage, 354 | version, 355 | archType 356 | ) 357 | commandPath = util.format( 358 | '%s/%s', 359 | extractedDirPath, 360 | commandPathInPackage 361 | ) 362 | } else { 363 | commandPath = packagePath 364 | } 365 | } catch (exception) { 366 | throw new Error(`Download ${tool.name} Failed! (url: ${downloadURL})`) 367 | } 368 | cachedToolPath = await toolCache.cacheFile( 369 | commandPath, 370 | tool.name, 371 | tool.name, 372 | version 373 | ) 374 | // eslint-disable-next-line no-console 375 | console.log(`${tool.name} version '${version}' has been cached`) 376 | } else { 377 | // eslint-disable-next-line no-console 378 | console.log(`Found in cache: ${tool.name} version '${version}'`) 379 | } 380 | 381 | const cachedCommand = path.join(cachedToolPath, tool.name) 382 | fs.chmodSync(cachedCommand, '777') 383 | return cachedCommand 384 | } 385 | 386 | // eslint-disable-next-line @typescript-eslint/explicit-function-return-type 387 | async function run() { 388 | if (!os.type().match(/^Linux/)) { 389 | throw new Error('The action only support Linux OS!') 390 | } 391 | 392 | let failFast = true 393 | if (core.getInput('fail-fast', {required: false}).toLowerCase() === 'false') { 394 | failFast = false 395 | } 396 | 397 | // Auto-detect architecture; allow explicit override to 'amd64' or 'arm64' if provided. 398 | let archType = detectArchType() 399 | console.log(`Detected archType: ${archType}`) 400 | const inputArch = core.getInput('arch-type', {required: false}).toLowerCase() 401 | console.log(`input archType: ${inputArch}`) 402 | if (inputArch === 'arm64' || inputArch === 'amd64') { 403 | archType = inputArch 404 | } 405 | 406 | let setupToolList: string[] = [] 407 | const setupTools = core.getInput('setup-tools', {required: false}).trim() 408 | if (setupTools) { 409 | setupToolList = setupTools 410 | .split('\n') 411 | .map(function(x) { 412 | return x.trim() 413 | }) 414 | .filter(x => x !== '') 415 | } 416 | 417 | // eslint-disable-next-line github/array-foreach 418 | Tools.forEach(async function(tool) { 419 | let toolPath = '' 420 | // By default, the action setup all supported Kubernetes tools, which mean 421 | // all tools can be setup when setuptools does not have any elements. 422 | if (setupToolList.length === 0 || setupToolList.includes(tool.name)) { 423 | let toolVersion = core 424 | .getInput(tool.name, {required: false}) 425 | .toLowerCase() 426 | if (toolVersion && toolVersion.startsWith('v')) { 427 | toolVersion = toolVersion.substr(1) 428 | } 429 | if (!toolVersion) { 430 | toolVersion = tool.defaultVersion 431 | } 432 | if (toolVersion === 'latest') { 433 | try { 434 | const resolved = await getLatestVersion(tool.name) 435 | // eslint-disable-next-line no-console 436 | console.log(`Resolved latest for ${tool.name}: ${resolved}`) 437 | toolVersion = resolved 438 | } catch (e) { 439 | if (failFast) { 440 | // eslint-disable-next-line no-console 441 | console.log(`Exiting immediately (fail fast) - [Reason] ${e}`) 442 | process.exit(1) 443 | } else { 444 | throw new Error(`Cannot resolve a version for ${tool.name}.`) 445 | } 446 | } 447 | } 448 | if (archType === 'arm64' && !tool.supportArm) { 449 | // eslint-disable-next-line no-console 450 | console.log( 451 | `The ${tool.name} does not support arm64 architecture, skip it` 452 | ) 453 | return 454 | } 455 | 456 | try { 457 | const cachedPath = await downloadTool(toolVersion, archType, tool) 458 | core.addPath(path.dirname(cachedPath)) 459 | toolPath = cachedPath 460 | } catch (exception) { 461 | if (failFast) { 462 | // eslint-disable-next-line no-console 463 | console.log(`Exiting immediately (fail fast) - [Reason] ${exception}`) 464 | process.exit(1) 465 | } 466 | } 467 | } 468 | core.setOutput(`${tool.name}-path`, toolPath) 469 | }) 470 | } 471 | 472 | run().catch(core.setFailed) 473 | --------------------------------------------------------------------------------