From 2e2e559fc69f829b385405c56fedf354746eff74 Mon Sep 17 00:00:00 2001 From: David Fridrich Date: Wed, 28 Jan 2026 20:38:57 +0100 Subject: [PATCH] support latest version --- .github/workflows/test-action.yml | 12 +- README.md | 8 +- action.yml | 6 +- index.js | 223 ++++++++++++++++++------------ 4 files changed, 154 insertions(+), 95 deletions(-) diff --git a/.github/workflows/test-action.yml b/.github/workflows/test-action.yml index 8edea35..9dc0a68 100644 --- a/.github/workflows/test-action.yml +++ b/.github/workflows/test-action.yml @@ -2,7 +2,7 @@ name: test-action on: [pull_request, workflow_dispatch] jobs: - test-default: + test-default-latest: strategy: matrix: os: [ubuntu-latest,windows-latest,macos-latest] @@ -52,6 +52,16 @@ jobs: - name: Verify func using path run: /tmp/func-bin/func version + test-custom-binary-source: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup func CLI with custom binarySource URL + uses: ./ + with: + binarySource: 'https://github.com/knative/func/releases/download/knative-v1.19.0/func_linux_amd64' + - run: func version + test-invalid-version: runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index 4d86eb5..82255b6 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,15 @@ GitHub Action to download and setup the func CLI. Automatically detects OS and a ```yaml - uses: functions-dev/action@main with: - version: 'v1.20.0' # optional - name: 'func' # optional + version: 'v1.20.0' # optional - uses latest as default ``` ## Inputs | Input | Description | Default | |-------|-------------|---------| -| `version` | Version to download (e.g. `v1.20.0`) | recent stable | +| `version` | Version to download (e.g. `v1.20.0`) | latest | | `name` | Binary name | `func` | -| `binary` | Specific binary to download | auto-detected | +| `binary` | Specific binary to download from GitHub release | auto-detected | | `destination` | Download directory | cwd | +| `binarySource` | Full URL for the func binary | empty (uses GitHub releases) | diff --git a/action.yml b/action.yml index e145d90..bdf1ce2 100644 --- a/action.yml +++ b/action.yml @@ -6,11 +6,11 @@ inputs: binary: description: '(optional) Binary you want to download (exact string expected), otherwise will be determined via the OS of GH Runner' version: - description: '(optional) Provide version to download. Any version in release pages works https://github.com/knative/func/tags' + description: '(optional) Version to download. Use "latest" or specify a version from release pages https://github.com/knative/func/tags' destination: - description: '(optional) Provide a path where to move the desired downloaded binary, otherwise cwd is used' + description: '(optional) Path where to move the desired downloaded binary, otherwise cwd is used' binarySource: - description: '(optional) Base URL for downloading binaries. Defaults to GitHub releases. Pattern: //' + description: '(optional) Full URL for downloading the binary. Overrides version if provided. Must be curl-able returning the func binary' runs: using: 'node20' main: 'index.js' diff --git a/index.js b/index.js index 68d5934..e29ec8a 100644 --- a/index.js +++ b/index.js @@ -3,112 +3,161 @@ const exec = require('@actions/exec'); const path = require('path'); const fs = require('fs'); -// Static version - update manually when new func releases are available -const DEFAULT_FUNC_VERSION = 'knative-v1.20.1'; +// Using latest as default +const DEFAULT_FUNC_VERSION = 'latest'; +const DEFAULT_BINARY_SOURCE = 'https://github.com/knative/func/releases/download'; +const DEFAULT_LATEST_BINARY_SOURCE = 'https://github.com/knative/func/releases/latest/download'; // Returns the binary name for the current OS/arch from GitHub releases function getOsBinName() { - const runnerOS = process.env.RUNNER_OS; - const runnerArch = process.env.RUNNER_ARCH; - - if (runnerOS === 'Linux') { - switch (runnerArch) { - case 'X64': return 'func_linux_amd64'; - case 'ARM64': return 'func_linux_arm64'; - case 'PPC64LE': return 'func_linux_ppc64le'; - case 'S390X': return 'func_linux_s390x'; - default: return 'unknown'; - } - } else if (runnerOS === 'macOS') { - return runnerArch === 'X64' ? 'func_darwin_amd64' : 'func_darwin_arm64'; - } else if (runnerOS === 'Windows') { - return 'func_windows_amd64.exe'; - } else { - return 'unknown'; - } + const osBinName = core.getInput('binary'); + if (osBinName !== "") { + return osBinName; + } + + const runnerOS = process.env.RUNNER_OS; + const runnerArch = process.env.RUNNER_ARCH; + + if (runnerOS === 'Linux') { + switch (runnerArch) { + case 'X64': return 'func_linux_amd64'; + case 'ARM64': return 'func_linux_arm64'; + case 'PPC64LE': return 'func_linux_ppc64le'; + case 'S390X': return 'func_linux_s390x'; + default: throw new Error(`unknown runner: ${runnerArch}`); + } + } else if (runnerOS === 'macOS') { + return runnerArch === 'X64' ? 'func_darwin_amd64' : 'func_darwin_arm64'; + } else if (runnerOS === 'Windows') { + return 'func_windows_amd64.exe'; + } else { + throw new Error(`unknown runner: ${runnerArch}`); + } +} + +function resolveFullPathBin() { + const destination = core.getInput('destination') || process.cwd(); + let bin = core.getInput('name') || 'func'; + if (process.env.RUNNER_OS === 'Windows' && !bin.endsWith('.exe')) { + bin += '.exe'; + } + if (!fs.existsSync(destination)) { + fs.mkdirSync(destination, { recursive: true }); + } + return path.resolve(destination, bin); } // Normalizes version to release tag format: knative-vX.Y.Z // Ex.: '1.16' or 'v1.16' will return 'knative-v1.16.0' function smartVersionUpdate(version) { - const versionRegex = /^(?knative-)?(?v?)(?\d+)\.(?\d+)(.(?\d+))?$/; - const match = version.match(versionRegex); - if (!match) { - throw new Error(`Invalid version format (${version}). Expected format: "1.16[.X]" or "v1.16[.X]"`); - } - const knprefix = 'knative-'; - const prefix = 'v'; - const patch = match.groups.patch ?? 0; - return `${knprefix}${prefix}${match.groups.major}.${match.groups.minor}.${patch}`; + const versionRegex = /^(?knative-)?(?v?)(?\d+)\.(?\d+)(\.(?\d+))?$/; + const match = version.match(versionRegex); + if (!match) { + throw new Error(`Invalid version format (${version}). Expected format: "1.16[.X]" or "v1.16[.X]"`); + } + const knprefix = 'knative-'; + const prefix = 'v'; + const patch = match.groups.patch ?? 0; + return `${knprefix}${prefix}${match.groups.major}.${match.groups.minor}.${patch}`; } -const DEFAULT_BINARY_SOURCE = 'https://github.com/knative/func/releases/download'; - // Downloads binary from release URL and makes it executable -async function downloadFuncBinary(version, osBinName, binPath, binarySource) { - const url = `${binarySource}/${version}/${osBinName}`; - core.info(`Downloading from: ${url}`); +async function downloadFuncBinary(url, binPath) { + core.info(`Downloading from: ${url}`); - await exec.exec('curl', ['-L', '--fail', '-o', binPath, url]); + await exec.exec('curl', ['-L', '--fail', '-o', binPath, url]); - if (!fs.existsSync(binPath)) { - throw new Error("Download failed, couldn't find the binary on disk"); - } + if (!fs.existsSync(binPath)) { + throw new Error("Download failed, couldn't find the binary on disk"); + } - if (process.env.RUNNER_OS !== 'Windows') { - await exec.exec('chmod', ['+x', binPath]); - } + if (process.env.RUNNER_OS !== 'Windows') { + await exec.exec('chmod', ['+x', binPath]); + } } // Adds binary directory to PATH for current and subsequent steps -async function addBinToPath(binPath) { - const dir = path.dirname(binPath); - fs.appendFileSync(process.env.GITHUB_PATH, `\n${dir}`); - - if (!process.env.PATH.includes(dir)) { - process.env.PATH = process.env.PATH + path.delimiter + dir; - core.info(`${dir} added to PATH`); - } +function addBinToPath(binPath) { + const dir = path.dirname(binPath); + fs.appendFileSync(process.env.GITHUB_PATH, `\n${dir}`); + + if (!process.env.PATH.split(path.delimiter).includes(dir)) { + process.env.PATH = process.env.PATH + path.delimiter + dir; + core.info(`${dir} added to PATH`); + } +} + +// Resolve download url based on given input +// binName: name of func binary when it is to be constructed for full URL +// (when not using binarySource) +function resolveDownloadUrl(binName) { + const binarySource = core.getInput('binarySource'); + if (binarySource !== "") { + core.info(`Using custom binary source: ${binarySource}`); + return binarySource; + } + + const versionInput = core.getInput('version') || DEFAULT_FUNC_VERSION; + if (versionInput.toLowerCase().trim() === DEFAULT_FUNC_VERSION) { + core.info("Using latest version..."); + return buildUrlString(DEFAULT_FUNC_VERSION); + } + const version = smartVersionUpdate(versionInput); + core.info(`Using specific version ${version}`); + return buildUrlString(version); + + function buildUrlString(version) { + return version === DEFAULT_FUNC_VERSION + ? `${DEFAULT_LATEST_BINARY_SOURCE}/${binName}` + : `${DEFAULT_BINARY_SOURCE}/${version}/${binName}`; + } } async function run() { - const osBinName = core.getInput('binary') || getOsBinName(); - if (osBinName === "unknown") { - core.setFailed("Invalid os binary determination, try setting it specifically using 'binary'"); - return; - } - - const versionInput = core.getInput('version') || DEFAULT_FUNC_VERSION; - const destination = core.getInput('destination') || process.cwd(); - const binarySource = core.getInput('binarySource') || DEFAULT_BINARY_SOURCE; - let bin = core.getInput('name') || 'func'; - if (process.env.RUNNER_OS === 'Windows' && !bin.endsWith('.exe')) { - bin += '.exe'; - } - - let version; - try { - version = smartVersionUpdate(versionInput); - } catch (error) { - core.setFailed(error.message); - return; - } - - if (!fs.existsSync(destination)) { - fs.mkdirSync(destination, { recursive: true }); - } - - const fullPathBin = path.resolve(destination, bin); - - try { - await downloadFuncBinary(version, osBinName, fullPathBin, binarySource); - } catch (error) { - core.setFailed(`Download failed: ${error.message}`); - return; - } - - await addBinToPath(fullPathBin); - await exec.exec(fullPathBin, ['version']); + let osBinName; + try { + osBinName = getOsBinName(); + } catch (error) { + core.setFailed(error.message); + return; + } + + let url; + try { + url = resolveDownloadUrl(osBinName); + } catch (error) { + core.setFailed(`Failed to resolve url: ${error.message}`); + return; + } + + let fullPathBin; + try { + fullPathBin = resolveFullPathBin(); + } catch (error) { + core.setFailed(error.message); + return; + } + + try { + await downloadFuncBinary(url, fullPathBin); + } catch (error) { + core.setFailed(`Download failed: ${error.message}`); + return; + } + + try { + addBinToPath(fullPathBin); + } catch (error) { + core.setFailed(error.message); + return; + } + + try { + await exec.exec(fullPathBin, ['version']); + } catch (error) { + core.setFailed(error.message); + return; + } } -run(); \ No newline at end of file +run();