Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
68 changes: 65 additions & 3 deletions .github/workflows/test-action.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,70 @@
name: test-action
on: [pull_request,workflow_dispatch]
on: [pull_request, workflow_dispatch]

jobs:
func:
test-default:
strategy:
matrix:
os: [ubuntu-latest,windows-latest,macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup func CLI
uses: ./
- run: func version

test-custom-version:
runs-on: ubuntu-latest
env:
TEST_VERSION: '1.19.0'
VERSION_OFFSET: 27 # internal version = minor + offset (v1.19 → v0.46)
steps:
- uses: actions/checkout@v4
- name: Setup func CLI with custom version
uses: ./
with:
version: 'v${{ env.TEST_VERSION }}'
- name: Verify version
run: |
MINOR=$(echo "$TEST_VERSION" | cut -d. -f2)
INTERNAL=$((MINOR + VERSION_OFFSET))
func version | grep -q "v0.${INTERNAL}"

test-custom-name:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup func CLI with custom name
uses: ./
with:
name: 'my-func'
- run: my-func version

test-custom-destination:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup func CLI with custom destination
uses: ./
with:
destination: '/tmp/func-bin'
- run: func version
- name: Verify func using path
run: /tmp/func-bin/func version

test-invalid-version:
runs-on: ubuntu-latest
steps:
- uses: functions-dev/action@main
- uses: actions/checkout@v4
- name: Setup func CLI with non-existing version (should fail)
id: invalid-version
uses: ./
with:
version: 'v99.99.99'
continue-on-error: true
- name: Verify action failed
run: |
if [ "${{ steps.invalid-version.outcome }}" != "failure" ]; then
echo "Expected action to fail with invalid version"
exit 1
fi
38 changes: 18 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
# Functions as Github Action!
Functions' Github Action, so you can get Functions in your workflows!
Automatically determines what os the GH Runner has to get the right binary.
You can change where you want the `func` to be downloaded with `destination` - regardless, it will be in `$PATH`!
By default, uses latest version, but you can specify which one you want using `version`.
# Func Action

GitHub Action to download and setup the func CLI. Automatically detects OS and architecture.

## Usage

`action.yml` -- action yaml with descriptions
```yaml
name: 'Func Action'
description: 'This action does custom stuff for it is custom'
inputs:
name:
description: '(optional) Name of the Function binary. It will be available in the runner under this name(defaults to "func")'
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'
destination:
description: '(optional) Provide a path where to move the desired downloaded binary, otherwise cwd is used'
runs:
using: 'node20'
main: 'index.js'
- uses: functions-dev/action@main
with:
version: 'v1.20.0' # optional
name: 'func' # optional
```

## Inputs

| Input | Description | Default |
|-------|-------------|---------|
| `version` | Version to download (e.g. `v1.20.0`) | recent stable |
| `name` | Binary name | `func` |
| `binary` | Specific binary to download | auto-detected |
| `destination` | Download directory | cwd |
6 changes: 4 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
name: 'Func Action'
description: 'This action does custom stuff for it is custom'
description: 'Downloads and sets up the func CLI for Functions'
inputs:
name:
description: '(optional) Name of the Function binary. It will be available in the runner under this name(defaults to "func")'
description: '(optional) Name of the binary (defaults to "func")'
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'
destination:
description: '(optional) Provide a 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: <url-base>/<version>/<os-bin-name>'
runs:
using: 'node20'
main: 'index.js'
154 changes: 70 additions & 84 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
const core = require('@actions/core');
const exec = require('@actions/exec')
const path = require('path')
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';

// change this accordingly
const DEFAULT_FUNC_VERSION = 'knative-v1.16.1'

// detect os system in Github Actions and determine binary name
// 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;
Expand All @@ -23,106 +22,93 @@ function getOsBinName() {
} else if (runnerOS === 'macOS') {
return runnerArch === 'X64' ? 'func_darwin_amd64' : 'func_darwin_arm64';
} else if (runnerOS === 'Windows') {
return 'func_windows_amd64';
return 'func_windows_amd64.exe';
} else {
return 'unknown';
}
}

// smartVersionParse will check validity of given version and fill in the parts
// to make it correct if possible.
// Ex.: '1.16' or 'v1.16' will return 'v1.16.0'
function smartVersionUpdate(version){
versionRegex = /^(?<knprefix>knative-)?(?<prefix>v?)(?<major>\d+)\.(?<minor>\d+)(.(?<patch>\d+))?$/;
let match = version.match(versionRegex);
if (match){
if (match.groups.knprefix == undefined){
match.groups.knprefix = "";
}
const prefix = 'v';
if (match.groups.patch == undefined) {
match.groups.patch = 0;
}
return `${match.groups.knprefix}${prefix}${match.groups.major}.${match.groups.minor}.${match.groups.patch}`;
}

core.setFailed(`Invalid version format (${version}). Expected format: "1.16[.X]" or "v1.16[.X]"`);
return undefined;
// 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 knprefix = 'knative-';
const prefix = 'v';
const patch = match.groups.patch ?? 0;
return `${knprefix}${prefix}${match.groups.major}.${match.groups.minor}.${patch}`;
}

/**
* @param {string} url - Full url to be curled
* @param {string} binPath - Full target path of the binary
*/
// download func, set as executable
async function cmdConstructAndRun(url,binPath){
const cmd = `curl -L -o "${binPath}" "${url}"`;
await exec.exec(cmd);

//check if downloaded successfully
if (!fs.existsSync(binPath)){
core.setFailed("Download failed, couldn't find the binary on disk");
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}`);

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");
}

await exec.exec(`chmod +x ${binPath}`);
if (process.env.RUNNER_OS !== 'Windows') {
await exec.exec('chmod', ['+x', binPath]);
}
}

/**
* @param {string} binPath - full path to Func binary
* */
async function addBinToPath(binPath){
dir = path.dirname(binPath)
// Write to $GITHUB_PATH, making it available for subsequent steps
// 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}`);

// add only if its not in PATH yet
if (!process.env.PATH.includes(dir)){
if (!process.env.PATH.includes(dir)) {
process.env.PATH = process.env.PATH + path.delimiter + dir;
core.info(`dir ${dir} added to $PATH`);
core.info(`${dir} added to PATH`);
}
}

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';
}
}

// -------------------------------------------------------------------------- \\
async function run(){
let version;
try {
version = smartVersionUpdate(versionInput);
} catch (error) {
core.setFailed(error.message);
return;
}

// Fetch value of inputs specified in action.yml or use defaults
if (!fs.existsSync(destination)) {
fs.mkdirSync(destination, { recursive: true });
}

// osBin refers to the exact name match of an existing binary available to
// download
const osBin = core.getInput('binary') || getOsBinName();
if (osBin == "unknown"){
core.setFailed("Invalid os binary determination, try setting it specifically using 'binary'");
}
// version to be downloaded
let version = core.getInput('version') || DEFAULT_FUNC_VERSION
// destination is a directory where to download the Func
const destination = core.getInput('destination') || process.cwd();
// bin refers to the name of the binary (Func) that will be downloaded (this
// is what it will be called)
const bin = core.getInput('name') || 'func';

version = smartVersionUpdate(version);

var url = `https://github.com/knative/func/releases/download/${version}/${osBin}`;
console.log(`URL: ${url}`);

fullPathBin = path.resolve(destination,bin);

// download Func
await cmdConstructAndRun(url,fullPathBin);

// add final binary to PATH (directory where the bin is ) and add it to
// GITHUB_PATH so it can be used bo subsequent 'runs'
await addBinToPath(fullPathBin);

await exec.exec("ls -la")
// run 'func version' as a test
await exec.exec(`${bin} version`);
const fullPathBin = path.resolve(destination, bin);

try {
await downloadFuncBinary(version, osBinName, fullPathBin, binarySource);
} catch (error) {
core.setFailed(error.message);
core.setFailed(`Download failed: ${error.message}`);
return;
}

await addBinToPath(fullPathBin);
await exec.exec(fullPathBin, ['version']);
}

run();
Loading
Loading