In our previous announcement, we announced that activities on Platform.sh were now shareable. If you need to send a member of our support team something fishy going on in your build hook, or get help from another member of your team about a database migration taking longer than expected, you can send them to the exact logline you need help with.
But like I said before, having the ability to link to a line of logs - if the format of that link is consistent (which it is) - means we can surface it wherever and whenever it’s useful.
Let’s take a minute to look at the link we can copy for an activity:
https://console.platform.sh/USER-ORG/PROJECT-ID/ENVIRONMENT/log/ACTIVITY-ID
So how can we leverage this new predictable link in our workflow? The first thing that comes to my mind is surfacing this link in my GitHub integrations.
Say I have a repository on GitHub, that’s been integrated to a Platform.sh project. My default branch is my production environment, and I’ve set up the standard integration defaults otherwise: environments are created automatically for pull requests. All of my team’s review takes place on these pull requests, and our checks on GitHub fail should the deployment for environment pr-X
fail.
When they fail, we have the same round-a-bout investigation path I described in the previous post to diagnose where things broke: go to the project, go to the environment, find the activity, view the log, find the line where things went wrong.
But since I can share a log for an activity directly, and since I know what the structure of that link is going to look like, I should be able to remove that added work by quite a bit. Here’s what I want:
- A pull request on GitHub provisions a development environment on Platform.sh.
- Each commit on the pull request results in a commit on that development environment.
- I need successful deployments to be a deciding factor on whether or not that PR is successful, not merging if the deployment fails.
- I want to know whether a deployment has failed.
- If a deployment has failed, I’d like the shareable activity link to be surfaced directly on GitHub where my team is reviewing, so that they can quickly investigate what happened.
The first two pieces are taken care of by our GitHub integration already, and the third item is something you’re likely already enforcing with your repository’s settings. The last two is what we’ll focus on here, and that’ll mean writing some tools that respond to deployments to trigger some other workflow that can surface our activity link when it fails.
Whatever method we choose, to “build” the activity link we need to be able to retrieve the following from each deployment:
- That and when an environment’s deployment has failed
- The user/organization that the project belongs to
- The project’s ID
- The environment ID
- The failed activity ID
Let’s get into it.
Waiting to deploy: Environment status via GitHub actions
If you’re not already familiar with GitHub actions, they are a method for including some configuration in YAML that can be used to trigger tests and other workflow steps in response to events that take place on your repository.
You can, for example, ensure that every commit pushed to your Python library can be used on a number of Python versions your users may have on their computers, or you can automatically publish your Node.js library to npm each time a new tag is pushed to the default branch.
Platform.sh connects pushed commits to deployments, usually within the scope of a pull request, and the GitHub integration will add a status check to your pull requests that reflect the status of that deployment.
Each time you push to a pull request on GitHub that has been integrated with Platform.sh, a webhook is triggered to let Platform.sh know that it should sync with the repository and deploy the new commit. After this has occurred, Platform.sh will respond back to GitHub the environment’s status, which becomes available at repos/:repos/status/:ref
. It will begin with a status of pending
as the environment deploys, but once finished another message from Platform.sh will update GitHub of the successful deployment:
{
"state": "success",
"statuses": [
{
...
"state": "success",
"description": "Platform.sh: Environment deployed",
"target_url": "http://pr-35-qsr3ccy-o3fc5w4n5iumy.eu-3.platformsh.site/",
"context": "platformsh",
}
],
…
}
The environment has deployed successfully, and Platform.sh sends along the generated environment URL in the target_url
attribute - which allows you to click the Details link on the Platform.sh check on a pull request and see the deployed environment.
If the environment fails to deploy, however, it will look more like this:
{
"state": "failure",
"statuses": [
{
...
"state": "failure",
"description": "Platform.sh: Environment deployment failed",
"target_url": "http://pr-35-qsr3ccy-o3fc5w4n5iumy.eu-3.platformsh.site/",
"context": "platformsh",
}
],
…
}
Exactly the same, just with a different state
message.
The target URL isn’t updated based on the failed deployment, so our Details link will simply take us to the environment URL which has failed to deploy. The failed URL doesn't help us much here, but it does seem like the opportune place to insert our activity link. So, from here we have a clear goal: detect the failed deployment and update the target_url
attribute with our failed activity link.
We start by creating some space for our workflows
mkdir .github
mkdir .github/workflows
touch .github/workflows/post-deploy.yaml
In a .github/workflows/post-deploy.yaml
file we can start building out the action. In the first lines, we define when this action should occur. For now, we can simply say that it should run on every push to all branches except our default branch - we’ll add some logic later on to make sure this only runs on pull requests. After that, we can expose a CLI token and a GitHub token as environment variables to be used by the action.
---
name: Post-deploy
on:
push:
branches-ignore:
- "main"
env:
PLATFORMSH_CLI_TOKEN: ${{ secrets.CLI_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Now it’s time to define a few jobs. The first one is simply meant to determine:
- Are we working from a pull request?
- What is the URL (
target_url
) of the environment associated with the pull request? - What’s the status of the deployment?
Below is the snippet for the job build
we’ll use to do this.
jobs:
build:
runs-on: ubuntu-latest
name: "Get environment URL"
outputs:
commit_status: ${{ steps.status.outputs.env_status }}
env_url: ${{ steps.url.outputs.env_url }}
integration_status: ${{ steps.wait.outputs.integration }}
steps:
- uses: actions/checkout@v2
- name: "Await deployment"
id: wait
run: |
COMMIT_STATUS="pending"
sleep 10
STATUSES=$(curl -s https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA | jq -r 'length')
if [ $STATUSES == 0 ]; then
echo "Not on a Platform.sh integrated environment. Skipping."
echo "::set-output name=integration::none"
else
until [ "$COMMIT_STATUS" == "success" ] || [ "$COMMIT_STATUS" == "failure" ]; do
sleep 30
ENV_URL=$(curl -s https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA | jq -r '.[0].target_url')
COMMIT_STATUS=$(curl -s https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA | jq -r '.[0].state')
echo "Waiting for Platform.sh environment to deploy ...."
echo " - $GITHUB_SHA"
echo " - $COMMIT_STATUS"
echo " - $ENV_URL"
done
echo "Environment deployed. Finished."
echo "::set-output name=integration::platformsh"
fi
- name: "Pass status"
id: status
if: steps.wait.outputs.integration == 'platformsh'
run: |
COMMIT_STATUS=$(curl -s https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA | jq -r '.[0].state')
echo "::set-output name=env_status::$COMMIT_STATUS"
- name: "Pass URL"
id: url
if: steps.wait.outputs.integration == 'platformsh'
run: |
ENV_URL=$(curl -s https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA | jq -r '.[0].target_url')
echo "::set-output name=env_url::$ENV_URL"
In summary, we are:
- Placing a request on the Status API.
- Verifying that this is associated with an environment on Platform.sh (deploying from a Pull request).
- Waiting for the status to update from
pending
tosuccess
orfailure
. In the case above, we’re literally placing requests on the status and waiting for it to change. This is unlikely to be the most efficient way of doing this, but it works - so ship it. - Passing that status and
target_url
value out of jobbuild
, so that those values will be accessible from subsequent jobs.
Now that the environment has deployed, we can pipe in these outputs to build the activity link in a new job called displaylogs
:
displaylogs:
name: "Display logs on failed deploy"
runs-on: ubuntu-latest
needs: build
if: needs.build.outputs.commit_status == 'failure'
steps:
- name: "Install the Platform.sh CLI"
run: |
# Install Platform.sh CLI
curl -sS https://platform.sh/cli/installer | php
- name: "Retrieve the logs"
id: activity
run: |
# Get data.
IFS='-' read -ra my_array <<< "${{ needs.build.outputs.env_url }}"
ENVIRONMENT="pr-${my_array[1]}"
IFS="." read -ra my_array <<< "${my_array[3]}"
PROJECT="${my_array[0]}"
ACTIVITY=$(~/.platformsh/bin/platform project:curl -p $PROJECT /environments/$ENVIRONMENT/activities | jq -r '.[0].id')
LOG_URL=https://console.platform.sh/projects/$PROJECT/$ENVIRONMENT/log/$ACTIVITY
echo " * PROJECT: $PROJECT"
echo " * ENVIRONMENT: $ENVIRONMENT"
echo " * ACTIVITY: $ACTIVITY"
echo " * LOGS: https://console.platform.sh/projects/$PROJECT/$ENVIRONMENT/log/$ACTIVITY"
echo "::set-output name=log_url::$LOG_URL"
- name: "Update integration target_url"
run: |
curl -s --location --request POST --header "Authorization: Bearer $GITHUB_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{"state": "failure", "target_url": "${{ steps.activity.outputs.log_url }}", "context": "platformsh", "description": "Platform.sh: Failed to deploy environment. View the activity log details."}' \
https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA
You’ll notice that job displaylogs
needs
the job build
- it will only run after build
has completed, and it will be able to access its outputs from the namespace needs.build.outputs.X
. It’s from this that we can restrict displaylogs
to make our update to only those cases when the environment deployment has failed. After this check, the job
- Installs the Platform.sh CLI.
- Uses the
target_url
to retrieve our project ID, and environment name for the pull request. - Places a request on the
projects/environments/:environment/activities
endpoint on the Platform.sh API to get the most recent failed activity (and its ID). - Builds the activity log URL from the project ID, environment ID, and activity ID at console.platform.sh.
- Places a final request onto the GitHub Status API, which updates the
target_url
value from Platform.sh to our activity URL.
With these changes in place, any failed deployment pushed to GitHub will be updated in this way. A successful deployment will provide the environment’s URL in the Details link of the integration status as usual, but anything that fails will take our team directly to the failed logs so that we can investigate what went wrong.
You can find the Gist of the GitHub action described above here.
Let us know where you’d like to see shareable activities by joining the Platform.sh Slack, and share with us how you’ve used it in your own workflow on our Community.