diff --git a/.github/actions/setup-action/action.yml b/.github/actions/setup-action/action.yml deleted file mode 100644 index 63fc965e..00000000 --- a/.github/actions/setup-action/action.yml +++ /dev/null @@ -1,186 +0,0 @@ -name: setup-action -description: Install the Unikraft CLI from GitHub releases -inputs: - version: - description: Version to install (latest, dev, or a release tag) - required: false - default: latest - github-token: - description: GitHub token used by gh for release resolution/downloads - required: false - default: ${{ github.token }} - token: - description: Authentication token for unikraft login - required: false - default: "" - organization: - description: Organization to associate the login with - required: false - default: "" -outputs: - version: - description: Resolved release tag that was installed - value: ${{ steps.resolve.outputs.tag }} -runs: - using: composite - steps: - - name: Resolve release tag - id: resolve - shell: bash - env: - GH_TOKEN: ${{ inputs['github-token'] || github.token }} - REPO: "unikraft/cli" - VERSION: ${{ inputs.version }} - run: | - set -euo pipefail - - if [[ -z "${VERSION}" ]]; then - VERSION="latest" - fi - - if [[ "${VERSION}" == "latest" ]]; then - tag="" - api_err="$(mktemp)" - if latest_json="$(gh api "repos/${REPO}/releases/latest" 2>"${api_err}")"; then - tag="$(echo "${latest_json}" | jq -r '.tag_name // empty')" - if [[ -z "${tag}" || "${tag}" == "null" ]]; then - rm -f "${api_err}" - echo "No latest release tag found in repository ${REPO}." >&2 - exit 1 - fi - else - err_msg="$(cat "${api_err}")" - rm -f "${api_err}" - if [[ -z "${err_msg}" ]]; then - err_msg="unknown error" - fi - echo "Failed to fetch latest release from ${REPO}: ${err_msg}" >&2 - exit 1 - fi - rm -f "${api_err}" - elif [[ "${VERSION}" == "dev" ]]; then - releases="$(gh release list -R "${REPO}" --limit 100 --json tagName,isDraft,isPrerelease)" - tag="$(echo "${releases}" | jq -r '[.[] | select(.isDraft == false and .isPrerelease == true)][0].tagName // empty')" - else - if [[ "${VERSION}" =~ ^v ]]; then - tag="${VERSION}" - else - tag="v${VERSION}" - fi - - api_err="$(mktemp)" - if ! gh api "repos/${REPO}/releases/tags/${tag}" >/dev/null 2>"${api_err}"; then - err_msg="$(cat "${api_err}")" - rm -f "${api_err}" - if [[ "${err_msg}" == *"HTTP 404"* ]]; then - echo "Release tag '${tag}' not found in repository ${REPO}." >&2 - elif [[ -n "${err_msg}" ]]; then - echo "Failed to fetch release tag '${tag}' from ${REPO}: ${err_msg}" >&2 - else - echo "Failed to fetch release tag '${tag}' from ${REPO}." >&2 - fi - exit 1 - fi - rm -f "${api_err}" - fi - - if [[ -z "${tag}" || "${tag}" == "null" ]]; then - echo "Failed to resolve a release tag for version '${VERSION}'." >&2 - exit 1 - fi - - echo "tag=${tag}" >> "${GITHUB_OUTPUT}" - - - name: Download release asset - id: download - shell: bash - env: - GH_TOKEN: ${{ inputs['github-token'] || github.token }} - REPO: "unikraft/cli" - TAG: ${{ steps.resolve.outputs.tag }} - run: | - set -euo pipefail - os="$(uname -s | tr '[:upper:]' '[:lower:]')" - arch="$(uname -m)" - - case "${arch}" in - x86_64) arch="amd64" ;; - aarch64|arm64) arch="arm64" ;; - *) - echo "Unsupported architecture: ${arch}" >&2 - exit 1 - ;; - esac - - case "${os}" in - linux|darwin) ;; - *) - echo "Unsupported OS: ${os}" >&2 - exit 1 - ;; - esac - - version="${TAG#v}" - filename="unikraft-cli_${version}_${os}_${arch}.tar.gz" - download_dir="$(mktemp -d)" - gh release download "${TAG}" --repo "${REPO}" --pattern "${filename}" --dir "${download_dir}" - echo "archive=${download_dir}/${filename}" >> "${GITHUB_OUTPUT}" - - - name: Install Unikraft CLI - shell: bash - env: - ARCHIVE: "${{ steps.download.outputs.archive }}" - run: | - set -euo pipefail - install_dir="${RUNNER_TEMP}/unikraft-cli/bin" - extract_dir="$(mktemp -d)" - mkdir -p "${install_dir}" - - tar -xzf "${ARCHIVE}" -C "${extract_dir}" - binary_path="" - match_count=0 - while IFS= read -r match; do - match_count=$((match_count + 1)) - if [[ ${match_count} -gt 1 ]]; then - echo "Multiple unikraft binaries found in ${ARCHIVE}" >&2 - exit 1 - fi - binary_path="${match}" - done < <(find "${extract_dir}" -type f -name unikraft -perm /111) - if [[ -z "${binary_path}" ]]; then - echo "unikraft binary not found in ${ARCHIVE}" >&2 - exit 1 - fi - - install -m 0755 "${binary_path}" "${install_dir}/unikraft" - echo "${install_dir}" >> "${GITHUB_PATH}" - - - name: Login to Unikraft Cloud - if: ${{ inputs.token != '' || inputs.organization != '' }} - shell: bash - env: - TOKEN_INPUT: ${{ inputs.token }} - ORGANIZATION: ${{ inputs.organization }} - run: | - set -euo pipefail - - if [[ -z "${TOKEN_INPUT}" && -n "${ORGANIZATION}" ]]; then - echo "setup-action: organization input requires token input" >&2 - exit 1 - fi - - if [[ -z "${TOKEN_INPUT}" ]]; then - exit 0 - fi - - token_file="$(mktemp)" - trap 'rm -f "${token_file}"' EXIT - chmod 0600 "${token_file}" - printf '%s' "${TOKEN_INPUT}" > "${token_file}" - - args=(login --token "${token_file}") - if [[ -n "${ORGANIZATION}" ]]; then - args+=(--organization "${ORGANIZATION}") - fi - - unikraft "${args[@]}" diff --git a/.github/workflows/test-setup-action.yaml b/.github/workflows/test-setup-action.yaml deleted file mode 100644 index 092cfec0..00000000 --- a/.github/workflows/test-setup-action.yaml +++ /dev/null @@ -1,148 +0,0 @@ -name: test-setup-action -permissions: - contents: read - -on: - pull_request: - branches: - - staging - paths: - - '.github/actions/setup-action/**' - - '.github/workflows/test-setup-action.yaml' - push: - branches: - - staging - paths: - - '.github/actions/setup-action/**' - - '.github/workflows/test-setup-action.yaml' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} - cancel-in-progress: true - -jobs: - setup-action-install: - runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'arc-unikraft-cli-ubuntu24.04-standard' }} - steps: - - uses: actions/checkout@v6 - - - name: Install Unikraft CLI (default) - uses: ./.github/actions/setup-action - - name: Capture default version - shell: bash - run: | - unikraft --version | tee "${RUNNER_TEMP}/unikraft-version-default.txt" - - - name: Install Unikraft CLI (latest) - uses: ./.github/actions/setup-action - with: - version: latest - - name: Capture latest version - shell: bash - run: | - unikraft --version | tee "${RUNNER_TEMP}/unikraft-version-latest.txt" - - - name: Install Unikraft CLI (dev) - uses: ./.github/actions/setup-action - with: - version: dev - - name: Capture dev version - shell: bash - run: | - unikraft --version | tee "${RUNNER_TEMP}/unikraft-version-dev.txt" - - - name: Install Unikraft CLI (pinned) - uses: ./.github/actions/setup-action - with: - version: 0.1.0-staging.71 - - name: Capture pinned version - shell: bash - run: | - unikraft --version | tee "${RUNNER_TEMP}/unikraft-version-pinned.txt" - - - name: Verify CLI versions - shell: bash - run: | - default_output="$(cat "${RUNNER_TEMP}/unikraft-version-default.txt")" - latest_output="$(cat "${RUNNER_TEMP}/unikraft-version-latest.txt")" - dev_output="$(cat "${RUNNER_TEMP}/unikraft-version-dev.txt")" - pinned_output="$(cat "${RUNNER_TEMP}/unikraft-version-pinned.txt")" - - if [[ -z "${default_output}" || -z "${latest_output}" || -z "${dev_output}" || -z "${pinned_output}" ]]; then - echo "One or more version outputs are empty" >&2 - exit 1 - fi - - extract_version() { - local label="$1" - local output="$2" - local version - - version="$(printf '%s\n' "${output}" | awk -F: '/^[[:space:]]*version[[:space:]]*:/ { gsub(/^[[:space:]]+/, "", $2); gsub(/[[:space:]]+$/, "", $2); print $2; exit }')" - if [[ -z "${version}" ]]; then - echo "${label} output did not include a version line: ${output}" >&2 - exit 1 - fi - - if [[ ! "${version}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?([+][0-9A-Za-z.-]+)?$ ]]; then - echo "${label} version has unexpected format: ${version}" >&2 - exit 1 - fi - - printf '%s' "${version}" - } - - default_version="$(extract_version "Default" "${default_output}")" - latest_version="$(extract_version "Latest" "${latest_output}")" - dev_version="$(extract_version "Dev" "${dev_output}")" - pinned_version="$(extract_version "Pinned" "${pinned_output}")" - echo "Default version: ${default_version}" - echo "Latest version: ${latest_version}" - echo "Dev version: ${dev_version}" - echo "Pinned version: ${pinned_version}" - - if [[ "${default_version}" != "${latest_version}" ]]; then - echo "Expected default and latest versions to match: default=${default_version} latest=${latest_version}" >&2 - exit 1 - fi - - if [[ "${dev_version}" != *-* ]]; then - echo "Expected dev version to be a prerelease: ${dev_version}" >&2 - exit 1 - fi - - if [[ "${pinned_version}" != *"0.1.0-staging.71"* ]]; then - echo "Expected pinned version 0.1.0-staging.71 in output: ${pinned_output}" >&2 - exit 1 - fi - - setup-action-login: - runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'arc-unikraft-cli-ubuntu24.04-standard' }} - steps: - - uses: actions/checkout@v6 - - - name: Install Unikraft CLI (login) - if: ${{ github.event_name == 'push' || !github.event.pull_request.head.repo.fork }} - uses: ./.github/actions/setup-action - with: - version: dev - token: ${{ secrets.UKC_TOKEN }} - - name: Verify login with metro list - if: ${{ github.event_name == 'push' || !github.event.pull_request.head.repo.fork }} - shell: bash - run: | - set -euo pipefail - metros="$(unikraft metro list -oquiet)" - echo "${metros}" - if [[ -z "${metros}" ]]; then - echo "No metros returned from unikraft metro list" >&2 - exit 1 - fi - while IFS= read -r line; do - if [[ -z "${line}" ]]; then - echo "Unexpected empty metro entry" >&2 - exit 1 - fi - done <<< "${metros}" - diff --git a/README.md b/README.md index 1df60cd9..5dde0c2a 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,21 @@ yum install unikraft-cli +
+GitHub Actions + +- Requires [GitHub Actions](https://docs.github.com/en/actions). + +Use the official [`unikraft/setup-action`](https://github.com/unikraft/setup-action) +to install the CLI in your workflow: + +```yaml +- name: Install Unikraft CLI + uses: unikraft/setup-action@v1 +``` + +
+
From Source