-
Notifications
You must be signed in to change notification settings - Fork 2
v2: Rewrite action as composite wrapper around the dhq CLI #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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,72 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: Test | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| push: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| branches: [main, v2] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pull_request: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workflow_dispatch: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Cross-platform smoke test: install the pinned CLI on every supported runner | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # and confirm it executes. Doesn't touch any DeployHQ resources. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| install: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: Install CLI (${{ matrix.os }}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| strategy: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fail-fast: false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| matrix: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| os: [ubuntu-latest, macos-latest, windows-latest] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runs-on: ${{ matrix.os }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show the workflow with line numbers (small file)
echo "=== .github/workflows/test.yml ==="
wc -l .github/workflows/test.yml
nl -ba .github/workflows/test.yml | sed -n '1,120p'
# Find action.yml (either root or within .github/actions)
echo
echo "=== Find action.yml files ==="
fd -t f -a action.yml . | head -n 50
# If root action.yml exists, print inputs section
if [ -f action.yml ]; then
echo
echo "=== action.yml (root) ==="
nl -ba action.yml | sed -n '1,200p'
fi
# If there's an action.yml under .github, print any inputs.cli-version defaults
echo
echo "=== Search for cli-version default ==="
rg -n "cli-version" -S .github action.yml || trueRepository: deployhq/deployhq-action Length of output: 194 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show the workflow with line numbers (small file)
echo "=== .github/workflows/test.yml ==="
wc -l .github/workflows/test.yml
nl -ba .github/workflows/test.yml | sed -n '1,160p'
# Find action.yml files
echo
echo "=== Find action.yml files ==="
fd -t f action.yml . | head -n 50
# If root action.yml exists, print inputs section
if [ -f action.yml ]; then
echo
echo "=== action.yml (root) ==="
nl -ba action.yml | sed -n '1,220p'
fi
# Search for cli-version default and any secrets gating
echo
echo "=== Search for cli-version / TEST_DEPLOYHQ_API_KEY / persist-credentials ==="
rg -n "cli-version|TEST_DEPLOYHQ_API_KEY|persist-credentials|permissions:" . || trueRepository: deployhq/deployhq-action Length of output: 194 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== .github/workflows/test.yml (line-numbered) ==="
wc -l .github/workflows/test.yml
awk '{printf "%4d\t%s\n", NR, $0}' .github/workflows/test.yml | sed -n '1,200p'
echo
echo "=== Find action.yml files ==="
fd -t f action.yml . | head -n 50
echo
echo "=== Print any action.yml files (first ~220 lines) and search inputs/defaults ==="
for f in $(fd -t f action.yml .); do
echo
echo "---- $f ----"
sed -n '1,220p' "$f"
done
echo
echo "=== Search for persist-credentials / permissions / TEST_DEPLOYHQ_API_KEY / cli-version ==="
rg -n "persist-credentials|permissions:|TEST_DEPLOYHQ_API_KEY|cli-version|actions/checkout@v4|actions/checkout@" -S . || trueRepository: deployhq/deployhq-action Length of output: 7534 🌐 Web query:
💡 Result: The default value of Citations: 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Locate scripts/install-cli.sh ==="
fd -t f install-cli.sh . || true
echo
echo "=== Print scripts/install-cli.sh (first 200 lines) ==="
sed -n '1,220p' scripts/install-cli.sh
echo
echo "=== Search for DHQ_VERSION usage ==="
rg -n "DHQ_VERSION|inputs\\.cli-version|cli-version" scripts -S || trueRepository: deployhq/deployhq-action Length of output: 3270 Pin
🔒 Suggested hardening- - uses: actions/checkout@v4
+ - uses: actions/checkout@<pinned-commit-sha> # v4.x
+ with:
+ persist-credentials: false
...
- - uses: actions/checkout@v4
+ - uses: actions/checkout@<pinned-commit-sha> # v4.x
+ with:
+ persist-credentials: false🧰 Tools🪛 zizmor (1.25.2)[warning] 20-20: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) [error] 20-20: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Run install-cli.sh | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shell: bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DHQ_VERSION: v0.17.1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Locate workflow =="
ls -la .github/workflows || true
sed -n '1,120p' .github/workflows/test.yml
echo
echo "== Find action.yml =="
ls -la .github || true
ls -la . || true
# Try common locations for action.yml
for f in "action.yml" ".github/action.yml" ".github/actions/action.yml" ".github/actions/test/action.yml"; do
if [ -f "$f" ]; then
echo "== Contents: $f =="
sed -n '1,200p' "$f"
fi
done
echo
echo "== Search for DHQ_VERSION and cli-version default =="
rg -n "DHQ_VERSION|cli-version|inputs:|action.yml" .github -S || true
rg -n "DHQ_VERSION|cli-version|inputs:" . -S --hidden || trueRepository: deployhq/deployhq-action Length of output: 8861 Avoid duplicating the dhq CLI version pin in this workflow
DHQ_VERSION: v0.17.1🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run: ./scripts/install-cli.sh | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: dhq --version | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shell: bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run: dhq --version | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # End-to-end dry-run against a real DeployHQ account. Gated on secrets so | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # external PRs don't fail; runs on push to v2/main and on workflow_dispatch. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dry-run: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: dhq deploy --dry-run | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Skip if secrets are missing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: gate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| API_KEY: ${{ secrets.TEST_DEPLOYHQ_API_KEY }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -z "$API_KEY" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "skip=true" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "::notice::TEST_DEPLOYHQ_API_KEY not set; skipping dry-run job." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "skip=false" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+41
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gate on all required dry-run secrets, not only API key. Line 46 checks only ✅ Suggested gate logic - name: Skip if secrets are missing
id: gate
env:
API_KEY: ${{ secrets.TEST_DEPLOYHQ_API_KEY }}
+ ACCOUNT: ${{ secrets.TEST_DEPLOYHQ_ACCOUNT }}
+ EMAIL: ${{ secrets.TEST_DEPLOYHQ_EMAIL }}
+ PROJECT: ${{ secrets.TEST_DEPLOYHQ_PROJECT }}
+ SERVER: ${{ secrets.TEST_DEPLOYHQ_SERVER }}
run: |
- if [[ -z "$API_KEY" ]]; then
+ missing=()
+ [[ -z "${API_KEY:-}" ]] && missing+=("TEST_DEPLOYHQ_API_KEY")
+ [[ -z "${ACCOUNT:-}" ]] && missing+=("TEST_DEPLOYHQ_ACCOUNT")
+ [[ -z "${EMAIL:-}" ]] && missing+=("TEST_DEPLOYHQ_EMAIL")
+ [[ -z "${PROJECT:-}" ]] && missing+=("TEST_DEPLOYHQ_PROJECT")
+ [[ -z "${SERVER:-}" ]] && missing+=("TEST_DEPLOYHQ_SERVER")
+
+ if ((${`#missing`[@]})); then
echo "skip=true" >> "$GITHUB_OUTPUT"
- echo "::notice::TEST_DEPLOYHQ_API_KEY not set; skipping dry-run job."
+ echo "::notice::Missing required test secrets: ${missing[*]}. Skipping dry-run job."
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Run the action (dry-run) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if: steps.gate.outputs.skip == 'false' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: deploy | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: ./ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| api-key: ${{ secrets.TEST_DEPLOYHQ_API_KEY }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| account: ${{ secrets.TEST_DEPLOYHQ_ACCOUNT }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| email: ${{ secrets.TEST_DEPLOYHQ_EMAIL }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| project: ${{ secrets.TEST_DEPLOYHQ_PROJECT }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server: ${{ secrets.TEST_DEPLOYHQ_SERVER }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dry-run: "true" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wait: "false" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Assert outputs are populated | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if: steps.gate.outputs.skip == 'false' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test -n "${{ steps.deploy.outputs.deployment_id }}" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| || { echo "deployment_id was empty"; exit 1; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "deployment_id=${{ steps.deploy.outputs.deployment_id }}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "status=${{ steps.deploy.outputs.status }}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # Changelog | ||
|
|
||
| ## v2.0.0 — Switch to the official DeployHQ CLI | ||
|
|
||
| This is a breaking rewrite. The action now wraps the [`dhq` CLI](https://github.com/deployhq/deployhq-cli) instead of POSTing to a webhook URL. To stay on the old behaviour, pin `@v1`. | ||
|
|
||
| ### Breaking changes | ||
|
|
||
| - **Auth**: webhook URL replaced by API key. New required inputs: `api-key`, `account`, `email`. | ||
| - **Input style**: inputs are now declared properly (`with:`) instead of passed via `env:`. The legacy `DEPLOYHQ_*` env vars are no longer recognised. | ||
| - **Removed inputs**: `DEPLOYHQ_WEBHOOK_URL`, `REPO_CLONE_URL`. The CLI resolves the repository from the project configuration. | ||
| - **Defaults changed**: `revision` defaults to `${{ github.sha }}` (was `"latest"`). `branch` no longer defaults to `main` — the CLI auto-resolves it from server config. | ||
| - **Runtime**: Docker (Alpine) action replaced by a composite action. Runs on Linux, macOS, and Windows runners, including self-hosted. | ||
|
|
||
| ### Added | ||
|
|
||
| - New inputs: `project`, `server`, `wait`, `timeout`, `dry-run`, `full`, `start-revision`, `extra-args`, `cli-version`. | ||
| - New outputs: `deployment_id`, `deployment_url`, `status`, `server`, `project`. | ||
| - Built-in wait-for-completion with timeout (polls `dhq deployments show` until terminal). | ||
| - Job step summary table with deployment ID, status, project, server, and link. | ||
| - Pinned CLI version with SHA-256 checksum verification on download. | ||
|
|
||
| ### Migration | ||
|
|
||
| See the **Migration from v1** section in `README.md`. | ||
|
|
||
| ## v1.x — Legacy Docker webhook action | ||
|
|
||
| Final webhook-based release. Maintained on the `v1` tag only. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # CLAUDE.md | ||
|
|
||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
|
|
||
| ## What this is | ||
|
|
||
| A GitHub composite action that triggers a deployment on DeployHQ by invoking the official `dhq` CLI (<https://github.com/deployhq/deployhq-cli>). v1 (Docker + `curl` to a webhook URL) lives on the `v1` git tag and is no longer maintained on `main`. | ||
|
|
||
| ## Architecture | ||
|
|
||
| `runs.using: composite` in `action.yml`. Two steps: | ||
|
|
||
| 1. **Install dhq CLI** — `scripts/install-cli.sh` detects `RUNNER_OS`/`RUNNER_ARCH`, downloads the pinned `dhq` archive from `github.com/deployhq/deployhq-cli/releases`, verifies its SHA-256 against the release's `checksums.txt`, extracts to `$RUNNER_TOOL_CACHE/dhq/<version>/<os>_<arch>`, and appends that dir to `$GITHUB_PATH`. Cross-platform: Linux, macOS, Windows (Git Bash); amd64 + arm64. | ||
| 2. **Deploy** — `scripts/deploy.sh` builds the `dhq deploy --json --non-interactive` argv from `INPUT_*` env vars, runs the CLI, parses the JSON envelope with `jq`, and emits action outputs + a `$GITHUB_STEP_SUMMARY` table. | ||
|
|
||
| ### The `--wait` trick | ||
|
|
||
| `dhq deploy --json` returns immediately after queueing — its built-in `--wait` is a no-op in JSON mode (see `internal/commands/deploy.go` in the CLI). So `scripts/deploy.sh` implements waiting itself by polling `dhq deployments show <id> -p <project> --json` every 5s until the status is terminal (`completed`/`failed`/`cancelled`), respecting `INPUT_TIMEOUT`. If this changes upstream, the wait loop can be removed. | ||
|
|
||
| ### Auth | ||
|
|
||
| Three required env vars passed straight through to the CLI: `DEPLOYHQ_API_KEY`, `DEPLOYHQ_ACCOUNT`, `DEPLOYHQ_EMAIL`. No `dhq auth login` call — the CLI reads these directly. | ||
|
|
||
| ### Outputs | ||
|
|
||
| `scripts/deploy.sh` writes to `$GITHUB_OUTPUT`: `deployment_id`, `deployment_url`, `status`, `server`, `project`. The URL is constructed locally as `https://${DEPLOYHQ_ACCOUNT}.deployhq.com/projects/<permalink>/deployments/<id>` — the API doesn't return one. If the URL pattern changes server-side, fix it here. | ||
|
|
||
| ### CLI version pin | ||
|
|
||
| The pinned default lives in **one place only**: `inputs.cli-version.default` in `action.yml`. Bump it there when validating against a new CLI release. Users can override per-workflow via the `cli-version` input. | ||
|
|
||
| ## Things to know before editing | ||
|
|
||
| - **Two shells, one language.** Scripts use bash (`#!/usr/bin/env bash`, `set -euo pipefail`). On Windows runners GitHub's `shell: bash` picks Git Bash; `jq`, `tar`, `unzip`, `curl`, and `shasum`/`sha256sum` are all available there. Don't assume GNU-only flags. | ||
| - **`jq` is a hard dependency.** Preinstalled on all GitHub-hosted runners; self-hosted users must install it. The script fails fast with a clear message. | ||
| - **Exit codes are meaningful.** `0` success/queued, `1` deploy failed, `2` cancelled, `124` wait timeout, anything else = CLI error propagated. | ||
| - **`extra-args` is word-split unquoted by design** (escape hatch for new CLI flags). Don't quote it. | ||
| - **No `Dockerfile`/`entrypoint.sh`** — those live on `@v1`. Don't reintroduce them. | ||
| - Consumers can pin `@v2`, `@v2.x.y`, `@v1` (legacy), or `@main` (rolling). | ||
|
|
||
| ## Local testing | ||
|
|
||
| ```sh | ||
| DHQ_VERSION=v0.17.1 \ | ||
| RUNNER_OS=$(uname -s) RUNNER_ARCH=$(uname -m) \ | ||
| GITHUB_ACTION_PATH="$PWD" GITHUB_PATH=/tmp/gh-path \ | ||
| ./scripts/install-cli.sh | ||
|
|
||
| DEPLOYHQ_API_KEY=... DEPLOYHQ_ACCOUNT=... DEPLOYHQ_EMAIL=... \ | ||
| INPUT_PROJECT=... INPUT_SERVER=... INPUT_REVISION=... \ | ||
| INPUT_WAIT=false INPUT_DRY_RUN=true \ | ||
| GITHUB_OUTPUT=/tmp/gh-output GITHUB_STEP_SUMMARY=/tmp/gh-summary \ | ||
| ./scripts/deploy.sh | ||
| ``` | ||
|
|
||
| (Set `GITHUB_OUTPUT`/`GITHUB_STEP_SUMMARY` to writable file paths when running outside Actions; they're appended-to, not created.) |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,46 +1,152 @@ | ||
| # GitHub Action to Trigger a Deployment on DeployHQ with a Webhook URL 🚀 | ||
| # DeployHQ — GitHub Action | ||
|
|
||
| This action calls the DeployHQ's [Webhook URL](https://www.deployhq.com/support/deployments/automatic-deployments/custom) created for your DeployHQ account to trigger a deployment on DeployHQ. | ||
| Trigger a deployment on [DeployHQ](https://www.deployhq.com/) from a GitHub workflow. Wraps the official [`dhq` CLI](https://github.com/deployhq/deployhq-cli) so customers, agents, and CI all share one tool. | ||
|
|
||
| ## Usage | ||
| > **Looking for the legacy webhook action?** That's v1. Pin `deployhq/deployhq-action@v1` to keep the old behaviour. See [Migration from v1](#migration-from-v1) below. | ||
|
|
||
| All sensitive variables should be [set as encrypted secrets](https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables) in the action's configuration. | ||
|
|
||
| ### Configuration Variables | ||
|
|
||
| | Key | Value | Suggested Type | Required | | ||
| |----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------- | ------------- | | ||
| | `DEPLOYHQ_WEBHOOK_URL` | **Required.** Your DeployHQ webhook URL. Can be found in your DeployHQ Dashboard, under "Automatic Deployments" | `secret` | **Yes** | | ||
| | `REPO_REVISION` | The revision you wish to deploy. Can also be set to "latest" if you wish to deploy the latest revision in your set branch. If not set, the default value is "latest". | `secret` | **No** | | ||
| | `REPO_BRANCH` | The branch your revision is on. If not set, the default value is set to "main". | `secret` | **No** | | ||
| | `DEPLOYHQ_EMAIL` | **Required.** Your DeployHQ user. For example, matias@barilla.com. | `secret` | **Yes** | | ||
| | `REPO_CLONE_URL` | The path to your repository (as entered in the Deploy UI). If not set, it will be generated with [GitHub Action's default environment variables](https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables). Nonetheless, we highly recommend setting this variable to avoid unexpected results. | `secret` | **No** | | ||
|
|
||
| ### `workflow.yml` Example | ||
|
|
||
| Place in a `.yml` file such as this one in your `.github/workflows` folder. [Refer to the documentation on workflow YAML syntax here.](https://help.github.com/en/articles/workflow-syntax-for-github-actions) | ||
| ## Quick start | ||
|
|
||
| ```yaml | ||
| name: Deploy my website in DeployHQ w/ my webhook URL | ||
| on: push | ||
| name: Deploy | ||
| on: | ||
| push: | ||
| branches: [main] | ||
|
|
||
| jobs: | ||
| deploy: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Trigger DeployHQ deployment | ||
| uses: deployhq/deployhq-action@v2 | ||
| with: | ||
| api-key: ${{ secrets.DEPLOYHQ_API_KEY }} | ||
| account: ${{ secrets.DEPLOYHQ_ACCOUNT }} | ||
| email: ${{ secrets.DEPLOYHQ_EMAIL }} | ||
| project: my-project | ||
| server: production | ||
| ``` | ||
|
|
||
| The action installs the pinned `dhq` CLI on the runner, calls `dhq deploy`, waits for the deployment to reach a terminal status, and fails the job if it didn't succeed. | ||
|
|
||
| ## Inputs | ||
|
|
||
| | Name | Required | Default | Description | | ||
| |---|---|---|---| | ||
| | `api-key` | **yes** | — | DeployHQ API key. Generate one in **Account Settings → API access**. | | ||
| | `account` | **yes** | — | Your DeployHQ account subdomain (e.g. `acme` for `acme.deployhq.com`). | | ||
| | `email` | **yes** | — | DeployHQ user email associated with the API key. | | ||
| | `project` | no | `""` | Project identifier or permalink. Falls back to `DEPLOYHQ_PROJECT` if unset. | | ||
| | `server` | no | `""` | Server identifier or name. Fuzzy-matched. Auto-selected if the project has only one server. | | ||
| | `revision` | no | `${{ github.sha }}` | Commit SHA to deploy. | | ||
| | `branch` | no | `""` | Branch the revision lives on. Auto-resolved from server config if omitted. | | ||
| | `wait` | no | `"true"` | Block until the deployment reaches a terminal status. Job exit code reflects the result. | | ||
| | `timeout` | no | `"0"` | Max seconds to wait when `wait=true`. `0` waits indefinitely. | | ||
| | `dry-run` | no | `"false"` | Preview the deploy without executing it. | | ||
| | `full` | no | `"false"` | Deploy the entire branch from the first commit (`--full`). | | ||
| | `start-revision` | no | `""` | Start an incremental deploy from this commit. | | ||
| | `extra-args` | no | `""` | Additional raw flags appended to `dhq deploy`. Escape hatch for newer CLI flags. | | ||
| | `cli-version` | no | pinned | Pin a specific `dhq` CLI release (e.g. `v0.17.1`). Defaults to the version this action was tested against. | | ||
|
|
||
| ## Outputs | ||
|
|
||
| | Name | Description | | ||
| |---|---| | ||
| | `deployment_id` | DeployHQ deployment identifier (e.g. `dep-abc123`). | | ||
| | `deployment_url` | Web URL of the deployment in DeployHQ. | | ||
| | `status` | Final status (`completed`/`failed`/`cancelled`/`timeout`) when `wait=true`, else the queued status. | | ||
| | `server` | Resolved server identifier. | | ||
| | `project` | Resolved project permalink. | | ||
|
|
||
| ```yaml | ||
| - id: deploy | ||
| uses: deployhq/deployhq-action@v2 | ||
| with: | ||
| api-key: ${{ secrets.DEPLOYHQ_API_KEY }} | ||
| account: ${{ secrets.DEPLOYHQ_ACCOUNT }} | ||
| email: ${{ secrets.DEPLOYHQ_EMAIL }} | ||
| project: my-project | ||
| server: production | ||
|
|
||
| - name: Open deployment | ||
| if: success() | ||
| run: echo "Deployed → ${{ steps.deploy.outputs.deployment_url }}" | ||
| ``` | ||
|
|
||
| ## Common patterns | ||
|
|
||
| ### Don't block the workflow on the deploy | ||
|
|
||
| ```yaml | ||
| - uses: deployhq/deployhq-action@v2 | ||
| with: | ||
| api-key: ${{ secrets.DEPLOYHQ_API_KEY }} | ||
| account: ${{ secrets.DEPLOYHQ_ACCOUNT }} | ||
| email: ${{ secrets.DEPLOYHQ_EMAIL }} | ||
| project: my-project | ||
| server: production | ||
| wait: "false" | ||
| ``` | ||
|
|
||
| ### Dry-run on pull requests | ||
|
|
||
| ```yaml | ||
| - uses: deployhq/deployhq-action@v2 | ||
| with: | ||
| api-key: ${{ secrets.DEPLOYHQ_API_KEY }} | ||
| account: ${{ secrets.DEPLOYHQ_ACCOUNT }} | ||
| email: ${{ secrets.DEPLOYHQ_EMAIL }} | ||
| project: my-project | ||
| server: staging | ||
| dry-run: "true" | ||
| ``` | ||
|
|
||
| ### Pass through a flag the action doesn't expose yet | ||
|
|
||
| ```yaml | ||
| - uses: deployhq/deployhq-action@v2 | ||
| with: | ||
| api-key: ${{ secrets.DEPLOYHQ_API_KEY }} | ||
| account: ${{ secrets.DEPLOYHQ_ACCOUNT }} | ||
| email: ${{ secrets.DEPLOYHQ_EMAIL }} | ||
| project: my-project | ||
| server: production | ||
| extra-args: "--copy-config --run-build" | ||
| ``` | ||
|
|
||
| # Put steps here to build your site, deploy it to a service, etc. | ||
| - name: Trigger deployment in DeployHQ w/ webhook URL | ||
| uses: deployhq/deployhq-action@main | ||
| env: | ||
| # All these values should be set as encrypted secrets in your repository settings | ||
| DEPLOYHQ_WEBHOOK_URL: ${{ secrets.DEPLOYHQ_WEBHOOK_URL }} | ||
| REPO_REVISION: ${{ secrets.REPO_REVISION }} | ||
| REPO_BRANCH: ${{ secrets.REPO_BRANCH }} | ||
| DEPLOYHQ_EMAIL: ${{ secrets.DEPLOYHQ_EMAIL }} | ||
| REPO_CLONE_URL: ${{ secrets.REPO_CLONE_URL }} | ||
| ## Requirements | ||
|
|
||
| - A DeployHQ API key (**Account Settings → API access**). | ||
| - Runner with `bash`, `curl`, and `jq` available. GitHub-hosted runners (Ubuntu, macOS, Windows) ship with all three. Self-hosted runners must install `jq`. | ||
|
|
||
| ## Migration from v1 | ||
|
|
||
| v1 was a Docker action that POSTed to a webhook URL. v2 is a composite action that calls the `dhq` CLI directly. | ||
|
|
||
| | v1 input (env var) | v2 input | Notes | | ||
| |---|---|---| | ||
| | `DEPLOYHQ_WEBHOOK_URL` | _(removed)_ | Replaced by API key auth. | | ||
| | `DEPLOYHQ_EMAIL` | `email` | Now declared as a proper action input. | | ||
| | `REPO_REVISION` | `revision` | Defaults to `github.sha` instead of `"latest"`. | | ||
| | `REPO_BRANCH` | `branch` | Default `main` removed — CLI auto-resolves from server config. | | ||
| | `REPO_CLONE_URL` | _(removed)_ | CLI looks up the repo from the project config. | | ||
| | _(n/a)_ | `api-key`, `account` | **New required inputs.** | | ||
| | _(n/a)_ | `server`, `project` | Target a specific server/project. | | ||
| | _(n/a)_ | `wait`, `timeout`, `dry-run`, `full`, `start-revision`, `extra-args`, `cli-version` | New behaviour controls. | | ||
|
|
||
| **To stay on v1**, pin to it: | ||
|
|
||
| ```yaml | ||
| uses: deployhq/deployhq-action@v1 | ||
| ``` | ||
|
|
||
| **To migrate to v2:** | ||
|
|
||
| 1. Generate an API key in DeployHQ (**Account Settings → API access**). | ||
| 2. Add `DEPLOYHQ_API_KEY` and `DEPLOYHQ_ACCOUNT` as repository secrets. (`DEPLOYHQ_EMAIL` you already have.) | ||
| 3. Switch your workflow to `with:` syntax (see [Quick start](#quick-start)). | ||
| 4. Set `project:` and `server:` explicitly — the webhook implied these; the API requires them. | ||
| 5. Remove `DEPLOYHQ_WEBHOOK_URL` from your secrets when no other workflow uses it. | ||
|
|
||
| ## License | ||
|
|
||
| This project is distributed under the [MIT license](LICENSE.md). | ||
| MIT. See [LICENSE](LICENSE). |
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.
Set explicit least-privilege
permissionsfor the workflow.There is no
permissions:block, so the workflow inherits repository defaults. Declare minimal permissions explicitly.🛡️ Suggested change
on: push: branches: [main, v2] pull_request: workflow_dispatch: +permissions: + contents: read + jobs:📝 Committable suggestion
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 1-73: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block
(excessive-permissions)
🤖 Prompt for AI Agents