Return http 500 code (via API or hook) if a deploy fails
P
Peter Nixey
When you trigger a deploy from CI via either a deploy hook or the API the trigger returns immediately. Which also means it does not communicate whether the deploy succeeded.
The screenshot below shows a CI process in which both deploys to render actually failed to deploy.
However since the actual deploy command doesn't persist through the deploy or return that information then you have to poll Render to find out whether it's succeeded.
Doing so is a non-trivial operation. This is a public script that does so:
However well intentioned they are, I don't use any uncertified scritps in CI processes becuase of the tremendous security risk and so it would would be excellent for Render to either:
- Offer their own, certified Github actions script to make it easy to poll for success
or (better still)
- Offer a way to make a synchronous call to Render which fails if the deploy fails
Update
___
I've now written the github script to do this and have shared it below. It's a bit of a waste for all of yoru customers to be battling through this though :/
Log In
k
kate
Hey Peter! I'm Kate, and I'm on the product team at Render. Thanks for sharing this - I can see what a painful process this is right now. We're planning to build deploy webhooks later this quarter (see https://feedback.render.com/features/p/webhooks-1), which I hope will help alleviate this issue for you!
P
Peter Nixey
kate hi and thanks for coming back to me and I really appreciate you linking me across to this. All functionality is good and helpful.
I’m not totally sure that would help in the case of CI though. For the webhook to work with a CI script it would need a webserver to be running which isn’t by default the case with the CI runner.
The thing that would be really helpful is a synchronous call that stays connected to the deployment process until the deploy has completed.
Thank you!
Peter
k
kate
Peter Nixey I see what you mean. It sounds like something like
render deploy my-cool-service --wait
would be more helpful for you.I'll definitely keep this in mind. We're thinking a lot about ways to improve the CI process on Render right now. I'll reach out via email - I'd love to understand more about your workflow!
P
Peter Nixey
kate exactly :)
P
Peter Nixey
Part 3
------
- name: Poll Deployment Status
id: poll_status
run: |
# Poll for deployment state
# Define success, failure, and wait statuses
SUCCESS_STATUSES=("live")
FAIL_STATUSES=("build_failed" "update_failed" "canceled" "pre_deploy_failed" "deactivated")
WAIT_STATUSES=("created" "build_in_progress" "update_in_progress" "pre_deploy_in_progress")
# Poll the deployment status
for i in {1..20}; do
DEPLOY_STATUS=$(curl --silent --header "authorization: Bearer ${{ secrets.render_api_key }}" \
--header "accept: application/json" \
https://api.render.com/v1/services/${{ inputs.service_id }}/deploys/${{ steps.deploy.outputs.deploy_id }} | jq -r '.status')
# Check for one of the success statuses
if [[ " ${SUCCESS_STATUSES[@]} " =~ " ${DEPLOY_STATUS} " ]]; then
echo "Deployment succeeded."
exit 0
fi
# Check for one of the failure statuses
if [[ " ${FAIL_STATUSES[@]} " =~ " ${DEPLOY_STATUS} " ]]; then
echo "Deployment failed."
exit 1
fi
# If still in progress, wait
if [[ " ${WAIT_STATUSES[@]} " =~ " ${DEPLOY_STATUS} " ]]; then
echo "Polling... current status: ${DEPLOY_STATUS}"
fi
sleep 10
done
echo "Deployment timed out."
exit 1
P
Peter Nixey
Part 2
-----
- name: Trigger Deploy and parse response
id: deploy
run: |
RESPONSE=$(curl --show-error --write-out "HTTPSTATUS:%{http_code}" \
--request POST \
--url https://api.render.com/v1/services/${{ inputs.service_id }}/deploys \
--header 'accept: application/json' \
--header 'authorization: Bearer ${{ secrets.render_api_key }}' \
--header 'content-type: application/json' \
--data '${{ steps.validate_inputs.outputs.deploy_data }}')
# Extract key parts from the response
RESPONSE_BODY=$(echo "$RESPONSE" | sed -e 's/HTTPSTATUS\:.*//g')
HTTP_STATUS=$(echo "$RESPONSE" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
DEPLOY_ID=$(echo "$RESPONSE_BODY" | jq -r '.id')
# Save response to output
echo "response_body=$RESPONSE_BODY" >> $GITHUB_OUTPUT
echo "http_status=$HTTP_STATUS" >> $GITHUB_OUTPUT
echo "deploy_id=$DEPLOY_ID" >> $GITHUB_OUTPUT
# Print response
echo "Response"
echo "----------"
echo "response_body=$RESPONSE_BODY"
echo "http_status=$HTTP_STATUS"
echo "deploy_id=$DEPLOY_ID"
- name: Check HTTP Response Status
id: check_status
run: |
# Determine HTTP status code and stop if unsuccessful
# Fail the job if the status code is not 2xx (successful)
if [[ "${{ steps.deploy.outputs.http_status }}" -lt 200 || "${{ steps.deploy.outputs.http_status }}" -ge 300 ]]; then
echo "Deployment failed with status code: ${{ steps.deploy.outputs.http_status }}"
exit 1
else
echo "Deployment succeeded with status code: ${{ steps.deploy.outputs.http_status }}"
fi
P
Peter Nixey
Update
----
I've now written the github action to actually do this process and while it works it's a pretty vivid illustration of how much work we have to do as your customers to get what would really make sense as core functionality.
It probably took about 7hrs to develop this script and if I hadn't had GPT4 I would literally have never done it. I hope you have a chance to fold this into your core offering:
Github action script to deploy, fail on a failed deploy and poll for success
Part 1
------
name: "Deploy to Render"
on:
workflow_call:
secrets:
render_api_key:
description: "The Render API key"
required: true
inputs:
docker_image_url:
description: "The Docker image URL for the deployment"
required: false
type: string
commit_id:
description: "The commit ID for the deployment"
required: false
type: string
service_id:
description: "The Render service ID"
required: true
type: string
timeout:
description: "Timeout for the request"
required: false
default: 5
type: number
jobs:
deploy:
name: Deploy to Render
runs-on: ubuntu-latest
steps:
- name: Validate inputs
id: validate_inputs
run: |
# Debug inputs
echo "docker_image_url: ${{ inputs.docker_image_url }}"
echo "commit_id: ${{ inputs.commit_id }}"
echo "service_id: ${{ inputs.service_id }}"
echo "timeout: ${{ inputs.timeout }}"
# Check whether to do image or commit based deploy
if [ -n "${{ inputs.docker_image_url }}" ]; then
DEPLOY_DATA='{
"imageUrl": "${{ inputs.docker_image_url }}"
}'
elif [ -n "${{ inputs.commit_id }}" ]; then
DEPLOY_DATA='{
"commitId": "${{ inputs.commit_id }}"
}'
else
echo "Either docker_image_url or commit_id must be provided."
exit 1
fi
# Use jq to convert DEPLOY_DATA to single-line JSON and export it
echo "$DEPLOY_DATA" | jq -c . > deploy_data.json
DEPLOY_DATA=$(cat deploy_data.json)
# Debug deploy data
echo "Structed deploy_data=$DEPLOY_DATA"
# Export DEPLOY_DATA for future steps
echo "deploy_data=$DEPLOY_DATA" >> $GITHUB_OUTPUT