• Overview
    Frameworks
    • Drupal
    • WordPress
    • Symfony
    • Magento
    • See all frameworks
    Features
    • Observability
    • Auto-scaling
    Solutions
    • Marketing Teams
    • Retail
    • Higher Education
  • Pricing
  • Featured articles
    • Switching to Platform.sh can help IT/DevOps organizations drive 219% ROI
    • Organizations, the ultimate way to manage your users and projects
  • Support
  • Docs
  • Contact
  • Login
  • Free Trial
Blog
Cover image

Share your failures with robots: surfacing activities with GitHub actions

activitiesgithubgithub actions
December 22, 2021
Chad Carlson
Chad Carlson
Manager, Developer Relations

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.

Success GitHub Status

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.

Failure GitHub Status

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 }}

You can add your Platform.sh CLI token as a repository or organization level secret.

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 to success or failure. 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 job build, 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.

Get the latest Platform.sh news and resources
Subscribe

Related Content

One codebase, many projects: Drupal multisite fleet management

One codebase, many projects: Drupal multisite fleet management

5
7
8
1
5
Deployments this week (including Fridays!)

Company

AboutSecurity and complianceTrust CenterBoard and investorsCareersPressContact us
5
7
8
1
5
Deployments this week (including Fridays!)
System StatusPrivacyTerms of ServiceImpressumWCAG ComplianceManage your cookie preferencesReport a security issue
© 2022 Platform.sh. All rights reserved.
Supported by Horizon 2020's SME Instrument - European Commission 🇪🇺