├── .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 | [](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 |
--------------------------------------------------------------------------------