Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 0
Warn on older versions of func#3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -3,15 +3,11 @@ const exec = require('@actions/exec'); | ||
| const path = require('path'); | ||
| const fs = require('fs'); | ||
| // 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'; | ||
| const LATEST = 'latest'; | ||
| // Returns the binary name for the current OS/arch from GitHub releases | ||
| function getOsBinName() { | ||
| const osBinName = core.getInput('binary'); | ||
| if (osBinName !== "") { | ||
| if (osBinName) { | ||
| return osBinName; | ||
| } | ||
| @@ -24,14 +20,14 @@ function getOsBinName() { | ||
| 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}`); | ||
| default: throw new Error(`unknown runner arch: ${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}`); | ||
| throw new Error(`unknown runner os: ${runnerOS}`); | ||
| } | ||
| } | ||
| @@ -47,117 +43,110 @@ function resolveFullPathBin() { | ||
| 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 = /^(?<knprefix>knative-)?(?<prefix>v?)(?<major>\d+)\.(?<minor>\d+)(\.(?<patch>\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 match = version.match(/^(?:knative-)?v?(\d+)\.(\d+)(?:\.(\d+))?$/); | ||
| if (!match) throw new Error(`Invalid version format (${version}). Expected format: "1.16[.X]" or "v1.16[.X]"`); | ||
| return `knative-v${match[1]}.${match[2]}.${match[3] ?? 0}`; | ||
| } | ||
| function resolveVersion() { | ||
| const version = core.getInput('version') || LATEST; | ||
| if (version.toLowerCase().trim() === LATEST) return LATEST; | ||
| return smartVersionUpdate(version); | ||
| } | ||
| function resolveDownloadUrl(version, binName) { | ||
| const binarySource = core.getInput('binarySource'); | ||
| if (binarySource) { | ||
| core.info(`Using custom binary source: ${binarySource}`); | ||
| return binarySource; | ||
| } | ||
| if (version === LATEST) { | ||
| core.info('Using latest version...'); | ||
| return `https://github.com/knative/func/releases/latest/download/${binName}`; | ||
| } | ||
| const knprefix = 'knative-'; | ||
| const prefix = 'v'; | ||
| const patch = match.groups.patch ?? 0; | ||
| return `${knprefix}${prefix}${match.groups.major}.${match.groups.minor}.${patch}`; | ||
| core.info(`Using specific version ${version}`); | ||
| return `https://github.com/knative/func/releases/download/${version}/${binName}`; | ||
| } | ||
| // Downloads binary from release URL and makes it executable | ||
| async function downloadFuncBinary(url, binPath) { | ||
| core.info(`Downloading from: ${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 (process.env.RUNNER_OS !== 'Windows') { | ||
| await exec.exec('chmod', ['+x', binPath]); | ||
| } | ||
| } | ||
| // Adds binary directory to PATH for current and subsequent steps | ||
| 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; | ||
| 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) { | ||
gauron99 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| 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}`); | ||
gauron99 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| return buildUrlString(version); | ||
| function buildUrlString(version) { | ||
| return version === DEFAULT_FUNC_VERSION | ||
| ? `${DEFAULT_LATEST_BINARY_SOURCE}/${binName}` | ||
| : `${DEFAULT_BINARY_SOURCE}/${version}/${binName}`; | ||
| } | ||
| } | ||
| async function run() { | ||
| let osBinName; | ||
| try { | ||
| osBinName = getOsBinName(); | ||
| } catch (error) { | ||
| core.setFailed(error.message); | ||
| async function warnStaleVersion(version) { | ||
gauron99 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| // Skip version check for 'latest' or custom binary source | ||
| if (version === LATEST || core.getInput('binarySource')) { | ||
| return; | ||
| } | ||
| let url; | ||
| let res; | ||
| try { | ||
gauron99 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| url = resolveDownloadUrl(osBinName); | ||
| res = await fetch('https://github.com/knative/func/releases/latest', { | ||
| method: 'HEAD', | ||
| redirect: 'manual', | ||
| }); | ||
| } catch (error) { | ||
| core.setFailed(`Failed to resolve url: ${error.message}`); | ||
| core.warning(`Failed to fetch latest version: ${error.message}`); | ||
| core.debug('Skipping stale version check'); | ||
| return; | ||
| } | ||
| let fullPathBin; | ||
| try { | ||
| fullPathBin = resolveFullPathBin(); | ||
| } catch (error) { | ||
| core.setFailed(error.message); | ||
| const loc = res.headers.get('location'); | ||
| if (!loc) { | ||
| core.warning('Could not determine latest version: no redirect location'); | ||
| core.debug('Skipping stale version check'); | ||
| return; | ||
| } | ||
| try { | ||
| await downloadFuncBinary(url, fullPathBin); | ||
| } catch (error) { | ||
| core.setFailed(`Download failed: ${error.message}`); | ||
| const latest = loc.split('/').pop(); | ||
| // Convert version string "vX.Y" to number (X*100 + Y) for comparison, | ||
| // e.g. "v1.21" → 121. | ||
| // Returns null if no match, otherwise multiplies major by 100 and adds minor | ||
| // (+ coerces string to number). | ||
| const toNum = (v) => { const m = v.match(/(\d+)\.(\d+)/); return m && m[1] * 100 + +m[2]; }; | ||
| const latestNum = toNum(latest); | ||
| const versionNum = toNum(version); | ||
| if (versionNum == null || latestNum == null) { | ||
| core.debug('could not parse version numbers to compare'); | ||
| return; | ||
| } | ||
| try { | ||
| addBinToPath(fullPathBin); | ||
| } catch (error) { | ||
| core.setFailed(error.message); | ||
| return; | ||
| const diff = latestNum - versionNum; | ||
| if (diff >= 3) { | ||
| core.warning(`You are using func ${version}, which is ${diff} minor versions behind the latest (${latest}). Upgrading is recommended.`); | ||
| } | ||
| } | ||
| try { | ||
| await exec.exec(fullPathBin, ['version']); | ||
| } catch (error) { | ||
| core.setFailed(error.message); | ||
| return; | ||
| } | ||
| async function run() { | ||
| const osBinName = getOsBinName(); | ||
| const version = resolveVersion(); | ||
| await warnStaleVersion(version); | ||
| const url = resolveDownloadUrl(version, osBinName); | ||
| const fullPathBin = resolveFullPathBin(); | ||
| await downloadFuncBinary(url, fullPathBin); | ||
| addBinToPath(fullPathBin); | ||
| await exec.exec(fullPathBin, ['version']); | ||
| } | ||
| run(); | ||
| run().catch(error => core.setFailed(error.message)); | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, looks good, but lets clean it up a bit and then we're done. I hope it's ok for you to go another round 🙈 .
I would avoid working with null values if I can. Especially in JavaScript. Being explicit makes the code easier to reason about, at least for me 😸. JavaScript is a permissive language 😆 it has
undefined, null, NaNas empty/null values and they behave differently 😆. Unlike in go wherenilchecks are common place => in JavaScript I would use them only when there is no other way. So in the JS code you write, where you can set non-null values => do it and check against that.