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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ jobs:
command: "compose up --project-name my-project"
```

### Disabling Output Capture
### Capturing Output

By default, the action captures the command's stdout as an output (`outputs.stdout`). For large deployments, this can exceed GitHub Actions' memory limits and cause the workflow to fail even though the deployment succeeded. If you don't need the stdout output, disable it:
Set `capture-output: true` to capture the command's stdout as an output (`outputs.stdout`). This is off by default because large deployments can exceed GitHub Actions' memory limits and cause the workflow to fail even though the deployment succeeded:

```yaml
jobs:
Expand All @@ -124,9 +124,41 @@ jobs:
- name: Deploy
uses: DefangLabs/defang-github-action@v2
with:
capture-output: false
capture-output: true
```

### Publishing the Deployment Summary

When the command is `compose up` (the default), the action surfaces the deployment result on the GitHub run instead of leaving it buried in the action log. After the command runs, the action calls `defang services --json` and:

- writes a table of deployed services (public endpoints as links, internal services as code) to the [job summary](https://github.blog/news-insights/product-news/supercharging-github-actions-with-job-summaries/), and
- exposes the primary public `https://` endpoint as the `endpoint` output.

Wire that output into the job's `environment.url` to get a clickable **"View deployment"** link on the run, the Environments tab, and the repo sidebar:

```yaml
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: ${{ steps.deploy.outputs.endpoint }}
permissions:
contents: read
id-token: write
steps:
# [...]
- name: Deploy
id: deploy
uses: DefangLabs/defang-github-action@v2
with:
command: "compose up"
```

Unlike `capture-output`, this writes straight to the job summary (1 MiB per step) rather than through the size-limited step-output channel, and `defang services` output is small and bounded — so it is safe even for large deployments where `capture-output` is disabled.

Set `summary: "false"` to skip the summary, or `summary: "true"` to force it for commands other than `compose up`.

### Full Example

Here is a full example of a GitHub workflow that does everything we've discussed so far:
Expand All @@ -142,6 +174,9 @@ on:
jobs:
test:
runs-on: ubuntu-latest
environment:
name: production
url: ${{ steps.deploy.outputs.endpoint }}
permissions:
contents: read
id-token: write
Expand All @@ -151,6 +186,7 @@ jobs:
uses: actions/checkout@v4

- name: Deploy
id: deploy
uses: DefangLabs/defang-github-action@v2
with:
cli-version: v3.5.2
Expand Down
93 changes: 72 additions & 21 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,21 @@ inputs:
required: false
default: "false"
capture-output:
description: "Capture the command's stdout as an output. Disable for large deployments to avoid GitHub Actions memory limits."
description: "Capture the command's stdout as the 'stdout' output. Off by default: large deployments can exceed GitHub Actions memory limits."
required: false
default: "true"
default: "false"
summary:
description: "After the command runs, write a table of deployed services to the GitHub job summary and expose the primary public endpoint as the 'endpoint' output. Uses 'defang services --json'. Defaults to enabled when 'command' is the default 'compose up'. Set to 'false' to disable, or 'true' to force-enable for other commands."
required: false
default: ""

outputs:
stdout:
description: "The stdout of the command. Only available when capture-output is 'true'."
value: ${{ steps.forward-output.outputs.stdout }}
endpoint:
description: "The primary public https endpoint of the deployed project (best-effort). Only set when the deployment summary runs (see 'summary')."
value: ${{ steps.summary.outputs.endpoint }}

runs:
using: "composite"
Expand Down Expand Up @@ -111,6 +118,18 @@ runs:
[ -n "${{ inputs['provider'] }}" ] && echo "DEFANG_PROVIDER=${{ inputs['provider'] }}" >> $GITHUB_ENV || true
[ -n "${{ inputs['stack'] }}" ] && echo "DEFANG_STACK=${{ inputs['stack'] }}" >> $GITHUB_ENV || true
[ -n "${{ inputs['project'] }}" ] && echo "COMPOSE_PROJECT_NAME=${{ inputs['project'] }}" >> $GITHUB_ENV || true
# Docker Compose (and the Defang CLI) natively read the compose file
# list from COMPOSE_FILE, so export that once instead of building a
# repeated -f/-f/-f list in every step. The unquoted echo collapses
# any whitespace (spaces or newlines) between filenames before tr
# joins them with ':'; COMPOSE_PATH_SEPARATOR pins the separator to
# ':' regardless of the runner OS.
if [ -n "$COMPOSE_FILES" ]; then
echo "COMPOSE_FILE=$(echo $COMPOSE_FILES | tr ' ' ':')" >> $GITHUB_ENV
echo "COMPOSE_PATH_SEPARATOR=:" >> $GITHUB_ENV
fi
env:
COMPOSE_FILES: ${{ inputs['compose-files'] }}

- name: Login to Defang
shell: bash
Expand All @@ -123,13 +142,6 @@ runs:
shell: bash
if: ${{ inputs['config-env-vars'] != '' || inputs['config-vars-init-random'] != '' || inputs['config-env-vars-init'] != '' }}
run: |
# Build compose file parameters
params=()
for filename in $COMPOSE_FILES; do
params+=("-f")
params+=("$filename")
done

# Check for empty environment variables (only for config vars from env)
empty=()
for source in $CONFIG_ENV_VARS $CONFIG_ENV_VARS_INIT; do
Expand All @@ -152,26 +164,25 @@ runs:
# Set random config variables
if [ -n "$CONFIG_VARS_INIT_RANDOM" ]; then
echo "Initialize randomly generated configs..."
echo defang config "${params[@]}" set --if-not-set --random $CONFIG_VARS_INIT_RANDOM
defang config "${params[@]}" set --if-not-set --random $CONFIG_VARS_INIT_RANDOM
echo defang config set --if-not-set --random $CONFIG_VARS_INIT_RANDOM
defang config set --if-not-set --random $CONFIG_VARS_INIT_RANDOM
fi

# Set init config variables
if [ -n "$CONFIG_ENV_VARS_INIT" ]; then
echo "Initialize configs..."
echo defang config "${params[@]}" set --if-not-set -e $CONFIG_ENV_VARS_INIT
defang config "${params[@]}" set --if-not-set -e $CONFIG_ENV_VARS_INIT
echo defang config set --if-not-set -e $CONFIG_ENV_VARS_INIT
defang config set --if-not-set -e $CONFIG_ENV_VARS_INIT
fi

# Set regular config variables
if [ -n "$CONFIG_ENV_VARS" ]; then
echo "Updating configs..."
echo defang config "${params[@]}" set -e $CONFIG_ENV_VARS
defang config "${params[@]}" set -e $CONFIG_ENV_VARS
echo defang config set -e $CONFIG_ENV_VARS
defang config set -e $CONFIG_ENV_VARS
fi
working-directory: ${{ inputs.cwd }}
env:
COMPOSE_FILES: ${{ inputs['compose-files'] }}
CONFIG_ENV_VARS: ${{ inputs['config-env-vars'] }}
CONFIG_ENV_VARS_INIT: ${{ inputs['config-env-vars-init'] }}
CONFIG_VARS_INIT_RANDOM: ${{ inputs['config-vars-init-random'] }}
Expand All @@ -183,11 +194,8 @@ runs:
working-directory: ${{ inputs.cwd }}
run: |
set -o pipefail

params=()
for filename in $COMPOSE_FILES; do
params+=("-f")
params+=("$filename")
done
if [[ -n "${{ inputs['mode'] }}" ]]; then
params+=("--mode=${{ inputs['mode'] }}")
fi
Expand All @@ -214,7 +222,6 @@ runs:
fi
env:
COMMAND: ${{ inputs['command'] }}
COMPOSE_FILES: ${{ inputs['compose-files'] }}
CONFIG_ENV_VARS: ${{ inputs['config-env-vars'] }}
CONFIG_ENV_VARS_INIT: ${{ inputs['config-env-vars-init'] }}
CONFIG_VARS_INIT_RANDOM: ${{ inputs['config-vars-init-random'] }}
Expand All @@ -231,3 +238,47 @@ runs:
env:
STDOUT: ${{ steps.command.outputs.stdout }}

- name: Deployment summary
id: summary
if: ${{ inputs['summary'] == 'true' || (inputs['summary'] == '' && (inputs['command'] == 'compose up' || startsWith(inputs['command'], 'compose up '))) }}
shell: bash
working-directory: ${{ inputs.cwd }}
run: |
set -o pipefail

if ! command -v jq >/dev/null 2>&1; then
echo "::notice::jq not found; skipping deployment summary"
exit 0
fi

# Fetch the deployed services as JSON. Compose files, stack, provider,
# and project come from the environment set earlier, so they don't
# need to be passed again.
# Only fall back to an empty array on failure, so a successful run
# never yields two concatenated JSON values.
if services=$(defang services --json 2>/dev/null); then
:
else
services='[]'
fi

# Expose the primary public endpoint (first https:// service) as an output.
endpoint=$(echo "$services" | jq -r 'map(select((.Endpoint // "") | startswith("https://"))) | .[0].Endpoint // empty')
echo "endpoint=$endpoint" >> "$GITHUB_OUTPUT"

# Render a services table to the job summary. Public endpoints become
# links; internal services are shown as code. Deployments with no
# services, or with services but no public endpoint, report
# "No services found." per the summary's contract.
{
echo "## 🚀 Deployed services"
echo ""
if [ -n "$endpoint" ] && [ "$(echo "$services" | jq 'length')" -gt 0 ]; then
echo "| Service | Status | Endpoint |"
echo "|---|---|---|"
echo "$services" | jq -r '.[] | "| \(.Service) | \(.Status) | " + (if ((.Endpoint // "") | startswith("https://")) then "[\(.Endpoint)](\(.Endpoint))" else "`\(.Endpoint)`" end) + " |"'
else
echo "_No services found._"
fi
} >> "$GITHUB_STEP_SUMMARY"
Comment thread
defangdevs marked this conversation as resolved.

Loading