Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
443c3cc7e1 | ||
|
|
7eb54a9e39 | ||
|
|
22c5cc3864 | ||
|
|
a0fc1ed3bf | ||
|
|
9814a57069 | ||
|
|
0597b11a08 | ||
|
|
0c2bcc2bf3 | ||
|
|
aa17f278e9 | ||
|
|
641b6b74db | ||
|
|
c370e32093 | ||
|
|
d9fb2b8307 | ||
|
|
b061303a52 | ||
|
|
d37d346399 | ||
|
|
36563347fa | ||
|
|
6ac935ba0c | ||
|
|
f7e6d667ef | ||
|
|
34e21c7e3f | ||
|
|
5fe2f65f42 | ||
|
|
3ed17e8ff6 | ||
|
|
4e7c8bb981 | ||
|
|
c6e917eb5e | ||
|
|
7446d29d60 | ||
|
|
22ac3ce88d | ||
|
|
8a18fa720b | ||
|
|
11bbbb9207 | ||
|
|
574fbc2143 | ||
|
|
b284f0ba18 | ||
|
|
558e44eea4 | ||
|
|
29d331ef6a | ||
|
|
28eb8b8850 | ||
|
|
692ba3e286 | ||
|
|
c368992ffb | ||
|
|
880d48e290 | ||
|
|
1b27c62a93 | ||
|
|
25403f865e | ||
|
|
f6e83f6681 | ||
|
|
b88fbf4ad6 | ||
|
|
fe5edd2387 | ||
|
|
50d08134cc | ||
|
|
1c88fc9f2b | ||
|
|
16b4bea4f4 | ||
|
|
34de7de05e | ||
|
|
f326ba7d79 |
29
.github/workflows/deploy.yaml
vendored
Normal file
29
.github/workflows/deploy.yaml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Deploy Action
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
- '!v1'
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Build latest dist/ folder
|
||||
run: |
|
||||
npm install -g pnpm
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run build
|
||||
- name: Upload dist/ folder
|
||||
run: |
|
||||
git config --global user.email "<41898282+github-actions[bot]@users.noreply.github.com>"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git checkout --orphan v1
|
||||
git add -f dist README.md LICENSE action.yaml
|
||||
git commit -m "chore: create ci release ($GITHUB_SHA)"
|
||||
git tag --force v1
|
||||
git push -f --tags origin v1
|
||||
37
.github/workflows/test.yaml
vendored
Normal file
37
.github/workflows/test.yaml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Test Action
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Build latest dist/ folder
|
||||
run: |
|
||||
npm install -g pnpm
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run build
|
||||
- name: Upload dist/ folder
|
||||
run: |
|
||||
git config --global user.email "<41898282+github-actions[bot]@users.noreply.github.com>"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git checkout --orphan ci
|
||||
git add -f dist README.md LICENSE action.yaml
|
||||
git commit -m "chore: create ci release ($GITHUB_SHA)"
|
||||
git push -f origin ci
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Setup tale/kubectl-action
|
||||
uses: tale/kubectl-action@ci
|
||||
with:
|
||||
base64-kube-config: ${{ secrets.KUBE_CONFIG }}
|
||||
- name: Test the output of `kubectl cluster-info`
|
||||
run: kubectl cluster-info
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
dist/
|
||||
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
}
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Aarnav Tale
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,8 +1,11 @@
|
||||
# kubectl-action
|
||||
|
||||
GitHub Action to manage a K8s (Kubernetes) cluster using kubectl.
|
||||
|
||||
# Usage
|
||||
## Usage
|
||||
|
||||
To use this action, add the following step to your GitHub Action workflow:
|
||||
|
||||
```yaml
|
||||
- uses: tale/kubectl-action@v1
|
||||
with:
|
||||
@@ -11,7 +14,8 @@ To use this action, add the following step to your GitHub Action workflow:
|
||||
|
||||
Keep in mind that the action expects a base64 encoded string of your Kubernetes configuration. The simplest way to do that is to run `cat $HOME/.kube/config | base64` and save that output as an action secret.
|
||||
|
||||
It's also possible to specify the version of the [kubectl](https://kubernetes.io/docs/reference/kubectl/) CLI to use. The current default release used by this action is `v1.23.0`.
|
||||
It's also possible to specify the version of the [kubectl](https://kubernetes.io/docs/reference/kubectl/) CLI to use. The current default release used by this action is the latest version.
|
||||
|
||||
```yaml
|
||||
- uses: tale/kubectl-action@v1
|
||||
with:
|
||||
@@ -20,6 +24,7 @@ It's also possible to specify the version of the [kubectl](https://kubernetes.io
|
||||
```
|
||||
|
||||
Once you've completed this setup, you have direct access to the `kubectl` binary and command in the rest of your actions. Here's a full example to give you some inspiration:
|
||||
|
||||
```yaml
|
||||
name: Kubectl Action
|
||||
|
||||
|
||||
15
action.yaml
15
action.yaml
@@ -8,18 +8,11 @@ inputs:
|
||||
kubectl-version:
|
||||
description: Version of the kubectl CLI to use
|
||||
required: false
|
||||
default: v1.23.0
|
||||
default: latest
|
||||
base64-kube-config:
|
||||
description: A base64 encoded reference to your authorization file (~/.kube/config)
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Configure kubectl CLI
|
||||
run: setup-kubectl.sh
|
||||
shell: bash
|
||||
- name: Authorize kubectl to the cluster
|
||||
run: login-kubectl.sh
|
||||
shell: bash
|
||||
env:
|
||||
BASE64_KUBE_CONFIG: ${{ inputs.base64-kube-config }}
|
||||
using: node16
|
||||
main: dist/index.js
|
||||
post: dist/index.js
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ ! -d "$HOME/.kube" ]; then
|
||||
mkdir -p $HOME/.kube
|
||||
fi
|
||||
|
||||
echo "$BASE64_KUBE_CONFIG" | base64 -d > $HOME/.kube/config
|
||||
21
package.json
Normal file
21
package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "kubectl-action",
|
||||
"version": "1.1.0",
|
||||
"scripts": {
|
||||
"dev": "ncc -smw --license licenses.txt build src/main.ts",
|
||||
"build": "ncc -sm --license licenses.txt build src/main.ts",
|
||||
"push": "np --no-cleanup --no-publish --no-tests --message 'chore: v%s'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"undici": "^5.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@vercel/ncc": "^0.36.0",
|
||||
"eslint": "^8.32.0",
|
||||
"eslint-config-tale": "^1.0.15",
|
||||
"np": "^7.6.3",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
3630
pnpm-lock.yaml
generated
Normal file
3630
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
curl -LO "https://dl.k8s.io/release/${{ inputs.kubectl-version }}/bin/linux/amd64/kubectl"
|
||||
curl -LO "https://dl.k8s.io/${{ inputs.kubectl-version }}/bin/linux/amd64/kubectl.sha256"
|
||||
echo "$(cat kubectl.sha256) kubectl" | sha256sum --check
|
||||
|
||||
mkdir $GITHUB_WORKSPACE/bin
|
||||
mv kubectl $GITHUB_WORKSPACE/bin
|
||||
echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH
|
||||
28
src/login.ts
Normal file
28
src/login.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { mkdir, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { env } from 'node:process'
|
||||
|
||||
import { debug, getInput, saveState, setFailed } from '@actions/core'
|
||||
|
||||
export async function setupKubeconfig() {
|
||||
debug('Running kubectl-action setupKubeconfig()')
|
||||
|
||||
if (env.HOME === undefined) {
|
||||
setFailed('$HOME is not defined')
|
||||
return
|
||||
}
|
||||
|
||||
const config = getInput('base64-kube-config', {
|
||||
required: true,
|
||||
trimWhitespace: true
|
||||
})
|
||||
|
||||
const decoded = Buffer.from(config, 'base64')
|
||||
.toString('utf8')
|
||||
|
||||
const path = join(env.HOME, '.kube')
|
||||
saveState('kubeconfig-path', path)
|
||||
|
||||
await mkdir(path, { recursive: true })
|
||||
await writeFile(join(path, 'config'), decoded, 'utf8')
|
||||
}
|
||||
22
src/main.ts
Normal file
22
src/main.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { debug, getState, setFailed } from '@actions/core'
|
||||
import { setupKubeconfig } from 'login'
|
||||
import { installKubectl } from 'setup'
|
||||
|
||||
const post = Boolean(getState('isPost'))
|
||||
|
||||
if (!post) {
|
||||
debug('Running kubectl-action setup')
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
new Promise(async () => {
|
||||
await installKubectl()
|
||||
debug('kubectl-action setup complete')
|
||||
|
||||
await setupKubeconfig()
|
||||
debug('kubectl-action kubeconfig setup complete')
|
||||
})
|
||||
// eslint-disable-next-line unicorn/prefer-top-level-await
|
||||
.catch(error => {
|
||||
setFailed('Failed to install kubectl (this is a bug in kubectl-action): ')
|
||||
debug(JSON.stringify(error))
|
||||
})
|
||||
}
|
||||
118
src/setup.ts
Normal file
118
src/setup.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { createHash, randomUUID } from 'node:crypto'
|
||||
import { mkdir, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { env, stdout } from 'node:process'
|
||||
import { clearLine, cursorTo } from 'node:readline'
|
||||
|
||||
import { addPath, debug, getInput, saveState, setFailed, warning } from '@actions/core'
|
||||
import { fetch } from 'undici'
|
||||
|
||||
export async function installKubectl() {
|
||||
debug('Running kubectl-action installKubectl()')
|
||||
|
||||
if (env.RUNNER_TEMP === undefined) {
|
||||
setFailed('$RUNNER_TEMP is not defined')
|
||||
return
|
||||
}
|
||||
|
||||
const input = getInput('kubectl-version', {
|
||||
required: false,
|
||||
trimWhitespace: true
|
||||
})
|
||||
|
||||
const version = input === 'latest' || input === '' ? await fetchLatestVersion() : input
|
||||
debug(`kubectl-version: ${version ?? 'undefined'}`)
|
||||
|
||||
if (!version?.startsWith('v')) {
|
||||
setFailed('Unable to determine the `kubectl` version to install')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`Installing kubectl version ${version}`)
|
||||
const kubectl = await downloadKubectl(version)
|
||||
|
||||
if (!kubectl) {
|
||||
return
|
||||
}
|
||||
|
||||
const path = join(env.RUNNER_TEMP, randomUUID())
|
||||
await mkdir(path, { recursive: true })
|
||||
saveState('kubectl-path', path)
|
||||
|
||||
console.log(`Installing kubectl to ${path}`)
|
||||
await writeFile(join(path, 'kubectl'), kubectl)
|
||||
addPath(path)
|
||||
}
|
||||
|
||||
// Fetches the latest kubectl version from the Kubernetes release server
|
||||
async function fetchLatestVersion() {
|
||||
const response = await fetch('https://dl.k8s.io/release/stable.txt')
|
||||
if (!response.ok) {
|
||||
setFailed(`Failed to fetch latest kubectl version with status ${response.status}`)
|
||||
return
|
||||
}
|
||||
|
||||
const version = await response.text()
|
||||
return version.trim()
|
||||
}
|
||||
|
||||
// Downloads the kubectl binary from the Kubernetes release server
|
||||
// Also runs a checksum verification on the downloaded binary
|
||||
async function downloadKubectl(version: string) {
|
||||
const url = `https://dl.k8s.io/release/${version}/bin/linux/amd64/kubectl`
|
||||
const hashUrl = `${url}.sha256`
|
||||
|
||||
console.log(`Downloading kubectl (${url})`)
|
||||
debug(`Downloading kubectl checksum (${hashUrl})`)
|
||||
|
||||
const hashResponse = await fetch(hashUrl)
|
||||
if (!hashResponse.ok) {
|
||||
debug(`Failed to download kubectl checksum with status ${hashResponse.status}`)
|
||||
warning(`Skipping checksum verification for kubectl ${version}`)
|
||||
}
|
||||
|
||||
const hash = hashResponse.ok ? await hashResponse.text() : ''
|
||||
|
||||
const response = await fetch(url)
|
||||
if (!response.ok || !response.body) {
|
||||
debug(`Failed to download kubectl with status ${response.status}`)
|
||||
setFailed(`Failed to download kubectl with status ${response.status}`)
|
||||
return
|
||||
}
|
||||
|
||||
const hashStream = createHash('sha256')
|
||||
const { body, headers } = response
|
||||
const size = Number(headers.get('content-length'))
|
||||
debug(`Downloaded kubectl (${size} bytes)`)
|
||||
|
||||
let downloaded = 0
|
||||
let progressed = 0
|
||||
const buffer = Buffer.alloc(size)
|
||||
|
||||
for await (const chunk of body as AsyncIterable<Buffer>) {
|
||||
buffer.write(chunk.toString('binary'), downloaded, 'binary')
|
||||
hashStream.update(chunk)
|
||||
downloaded += chunk.length
|
||||
|
||||
if (Math.floor((downloaded / size) * 80) > progressed) {
|
||||
clearLine(stdout, 0)
|
||||
cursorTo(stdout, 0)
|
||||
|
||||
progressed++
|
||||
stdout.write(`[${'='.repeat(progressed)}>${' '.repeat(80 - progressed)}]`)
|
||||
}
|
||||
}
|
||||
|
||||
clearLine(stdout, 0)
|
||||
cursorTo(stdout, 0)
|
||||
console.log(`[${'='.repeat(80)}]`)
|
||||
|
||||
const hashSum = hashStream.digest('hex')
|
||||
if (hashResponse.ok && hashSum !== hash) {
|
||||
debug(`Checksum verification failed for kubectl ${version}`)
|
||||
setFailed(`Checksum verification failed for kubectl ${version}`)
|
||||
return
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
14
src/teardown.ts
Normal file
14
src/teardown.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { rm } from 'node:fs/promises'
|
||||
|
||||
import { debug, getState } from '@actions/core'
|
||||
|
||||
export async function teardown() {
|
||||
debug('Running kubectl-action teardown()')
|
||||
console.log('Removing kubectl and kubeconfig')
|
||||
|
||||
const path = getState('kubectl-path')
|
||||
await rm(path, { recursive: true, force: true })
|
||||
|
||||
const configPath = getState('kubeconfig-path')
|
||||
await rm(configPath, { recursive: true, force: true })
|
||||
}
|
||||
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"exclude": ["dist"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src",
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"target": "ESNext",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user