name: Release CLI on: push: tags: - "cli/v*.*.*" jobs: pypi: runs-on: ubuntu-latest environment: name: release-cli permissions: contents: read # needed to checkout the repo on private repos (no-op on public) id-token: write timeout-minutes: 10 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # ratchet:astral-sh/setup-uv@v8.2.0 with: enable-cache: false version: "0.11.25" - run: | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 uv build --wheel GOOS=linux GOARCH=amd64 CGO_ENABLED=0 ONYX_CLI_REUSE_BINARY=1 \ ONYX_CLI_WHEEL_PLATFORM_TAG=musllinux_1_2_x86_64 \ uv build --wheel GOOS=linux GOARCH=arm64 CGO_ENABLED=0 uv build --wheel GOOS=linux GOARCH=arm64 CGO_ENABLED=0 ONYX_CLI_REUSE_BINARY=1 \ ONYX_CLI_WHEEL_PLATFORM_TAG=musllinux_1_2_aarch64 \ uv build --wheel for goos in windows darwin; do for goarch in amd64 arm64; do GOOS="$goos" GOARCH="$goarch" uv build --wheel done done working-directory: cli - name: Upload built wheels uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: onyx-cli-wheels path: cli/dist/*.whl if-no-files-found: error retention-days: 7 - run: uv publish working-directory: cli docker-amd64: runs-on: - runs-on - runner=2cpu-linux-x64 - run-id=${{ github.run_id }}-cli-amd64 environment: deploy permissions: contents: read # needed to checkout the repo on private repos (no-op on public) id-token: write timeout-minutes: 30 outputs: digest: ${{ steps.build.outputs.digest }} env: REGISTRY_IMAGE: onyxdotapp/onyx-cli steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # ratchet:aws-actions/configure-aws-credentials@v6.2.0 with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 # ratchet:aws-actions/aws-secretsmanager-get-secrets@v3.0.1 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Build and push AMD64 id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: ./cli file: ./cli/Dockerfile platforms: linux/amd64 cache-from: type=registry,ref=${{ env.REGISTRY_IMAGE }}:latest cache-to: type=inline outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true docker-arm64: runs-on: - runs-on - runner=2cpu-linux-arm64 - run-id=${{ github.run_id }}-cli-arm64 environment: deploy permissions: contents: read # needed to checkout the repo on private repos (no-op on public) id-token: write timeout-minutes: 30 outputs: digest: ${{ steps.build.outputs.digest }} env: REGISTRY_IMAGE: onyxdotapp/onyx-cli steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # ratchet:aws-actions/configure-aws-credentials@v6.2.0 with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 # ratchet:aws-actions/aws-secretsmanager-get-secrets@v3.0.1 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Build and push ARM64 id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: ./cli file: ./cli/Dockerfile platforms: linux/arm64 cache-from: type=registry,ref=${{ env.REGISTRY_IMAGE }}:latest cache-to: type=inline outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true merge-docker: needs: - docker-amd64 - docker-arm64 runs-on: - runs-on - runner=2cpu-linux-x64 - run-id=${{ github.run_id }}-cli-merge environment: deploy permissions: id-token: write timeout-minutes: 10 env: REGISTRY_IMAGE: onyxdotapp/onyx-cli steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # ratchet:aws-actions/configure-aws-credentials@v6.2.0 with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 # ratchet:aws-actions/aws-secretsmanager-get-secrets@v3.0.1 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Create and push manifest env: AMD64_DIGEST: ${{ needs.docker-amd64.outputs.digest }} ARM64_DIGEST: ${{ needs.docker-arm64.outputs.digest }} TAG: ${{ github.ref_name }} run: | SANITIZED_TAG="${TAG#cli/}" IMAGES=( "${REGISTRY_IMAGE}@${AMD64_DIGEST}" "${REGISTRY_IMAGE}@${ARM64_DIGEST}" ) if [[ "$TAG" =~ ^cli/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then docker buildx imagetools create \ -t "${REGISTRY_IMAGE}:${SANITIZED_TAG}" \ -t "${REGISTRY_IMAGE}:latest" \ "${IMAGES[@]}" else docker buildx imagetools create \ -t "${REGISTRY_IMAGE}:${SANITIZED_TAG}" \ "${IMAGES[@]}" fi govulncheck: needs: - pypi runs-on: ubuntu-latest permissions: contents: read # needed to checkout the repo for SARIF source locations actions: read # needed to download the wheel artifact from the release job security-events: write # needed for SARIF uploads timeout-minutes: 10 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # ratchet:actions/download-artifact@v4 with: name: onyx-cli-wheels path: cli/dist - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # zizmor: ignore[cache-poisoning] with: go-version: "1.26.4" cache: false - name: Install govulncheck run: | GOBIN="${RUNNER_TEMP}/bin" go install golang.org/x/vuln/cmd/govulncheck@v1.3.0 echo "${RUNNER_TEMP}/bin" >> "$GITHUB_PATH" - name: Run govulncheck on released wheel binaries run: | set -euo pipefail scan_exit=0 mkdir -p wheel-binaries govulncheck-results/raw for wheel in cli/dist/*.whl; do wheel_name="$(basename "$wheel" .whl)" if [[ "$wheel_name" == *-musllinux_* ]]; then echo "Skipping duplicate static Linux binary scan for $wheel_name" continue fi extract_dir="wheel-binaries/${wheel_name}" sarif_file="govulncheck-results/raw/${wheel_name}.sarif" unzip -q "$wheel" -d "$extract_dir" binary="$( find "$extract_dir" \ \( -path '*.data/scripts/onyx-cli' -o -path '*.data/scripts/onyx-cli.exe' \) \ -type f \ -print -quit )" if [ -z "$binary" ]; then echo "No onyx-cli binary found in $wheel" >&2 exit 1 fi if ! govulncheck -mode=binary "$binary"; then scan_exit=1 fi govulncheck -mode=binary -format=sarif "$binary" > "$sarif_file" done python3 <<'PY' import json from pathlib import Path raw_results_dir = Path("govulncheck-results/raw") merged = { "version": "2.1.0", "$schema": "https://json.schemastore.org/sarif-2.1.0.json", "runs": [], } sarif_paths = sorted(raw_results_dir.glob("*.sarif")) if not sarif_paths: raise SystemExit("No govulncheck SARIF files were generated") for sarif_path in sarif_paths: data = json.loads(sarif_path.read_text()) for run in data.get("runs", []): run["automationDetails"] = { "id": f"onyx-cli-govulncheck/{sarif_path.stem}", } results = [] upload_levels = {"error", "warning"} for result in run.get("results", []): if result.get("level") not in upload_levels: continue if not result.get("locations"): result["locations"] = [ { "physicalLocation": { "artifactLocation": {"uri": "cli/go.mod"}, "region": {"startLine": 1}, } } ] results.append(result) run["results"] = results merged["runs"].append(run) if not merged["runs"]: raise SystemExit("No govulncheck SARIF runs were generated") Path("govulncheck-results.sarif").write_text(json.dumps(merged)) PY exit "$scan_exit" - name: Upload govulncheck scan results to GitHub Security tab if: always() uses: github/codeql-action/upload-sarif@ba454b8ab46733eb6145342877cd148270bb77ab with: sarif_file: govulncheck-results.sarif