Github Projects, Issues and Datadog Integration

Published: Dec 15, 2021 by Isaac Johnson

Now that we have a live production workflow in Github Actions, we should explore Github Issues for Issue and Feature tracking. Can we use Github as a sufficient replacement for Azure DevOps?

Moreover, if we have our flows in Github Actions, how can we monitor and track our CI pipelines? We’ll also integrate Datadog for both event and CI tracking, walk through alert escalations with Pagerduty and discuss methods of change notification.

Lastly, we will dig into CODEOWNERS files and the basics of Github PR automated reviewers.

Project setup

First, let’s create a simple project in our repo named MyProjectBoard

/content/images/2021/12/gh-part3-01.png

This creates a straightforward Kanban board with reviews. On the right we have open Github Issues (tickets) and then the state columns into which we can drag them.

/content/images/2021/12/gh-part3-02.png

We can drag and drop issues to assign them to a state column.

/content/images/2021/12/gh-part3-03.png

Similar to AzDO we can see the bi-directional linking of Issues and PRs:

/content/images/2021/12/gh-part3-04.png

Another area we may wish to tweak is the Board Automations. Here we see what rules are in play to automatically move Issues to Done, such as being included in a PR that merges.

/content/images/2021/12/gh-part3-05.png

Labels and Milestones

The other places that we might want to adjust are Labels and Milestones.

Milestones are a fancy way of saying a “Release”. It bundles PRs and Issues into an object with a release date:

/content/images/2021/12/gh-part3-06.png

Labels (or issue tags) are a way of categorizing issues. In AzDO I would likely use an Issue type for similar things.

/content/images/2021/12/gh-part3-07.png

A quick example

A colleague of mine, Chad Prey, recently suggested I check out a project he worked on called “vCluster” that can create smaller virtual clusters in Minikube. Sort of inception-like, it seems like a cluster in a cluster (akin to k3d).

I want to start to capture these external blog suggestions into Github issues (as you recall back in June we implemented the feedback form above that creates an AzDO Work Item for such things)

Let’s first create an Issue Label for “External Suggestion”

/content/images/2021/12/gh-part3-08.png

First, I’ll do a quick note in the To-Do column to capture the suggestion

/content/images/2021/12/gh-part3-09.png

Next, we can use the drop-down to convert it to an issue.

/content/images/2021/12/gh-part3-10.png

I have a thing about using YAML in issues, so for now, just go with it

/content/images/2021/12/gh-part3-11.png

Now I can set the label on the issue from within the board:

/content/images/2021/12/gh-part3-12.png

I added a not for a WIP and a ticket for this blog post so my Board now looks as such:

/content/images/2021/12/gh-part3-13.png

And I can see the same active tickets under Issues

/content/images/2021/12/gh-part3-14.png

Via REST API

We can also get our GH Issues via the GH Rest API (and since this repo is private, that requires auth with our PAT or OAuth token):

$ curl -u idjohnson:$GHTOKEN  -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/idjohnson/jekyll-blog/issues 2>/dev/null| jq '.[] | .title'
"Github Boards (Actions Part 3)"
"Chad P suggests vClusts for OSX/Minikube"

Since my body has YAML, i can pull the fields from that as well:

$ curl -u idjohnson:$GHTOKEN  -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/idjohnson/jekyll-blog/issues 2>/dev/null| jq -r '.[] | .body' | grep 'requestor: '
requestor: self
requestor: Chad Prey

The REST API uses parameters to control field queries. So if I wanted to pull the last 10 closed issues:

$ curl -u idjohnson:$GHTOKEN  -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/idjohnson/jekyll-blog/issues?state=closed 2>/dev/null| jq -r '.[] | .title' | head -n10
Feature - GH Workflow Implementation 
Feature/impl gh workflow
Feature/gh actions
Feature/2021 11 24 newrelic2
Feature/2021 11 17 newrelic
Feature/2021 11 10 kk8s upgrade
update with notifications
Feature/zipkin
Feature/lightstep
Feature/crossplane demo

Or perhaps I’m interested to know what was closed just this month:

$ curl -u idjohnson:$GHTOKEN  -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/idjohnson/jekyll-blog/issues?state=closed\&since=2021-12-01T00:00:0
0CST 2>/dev/null | jq '.[] | .title
'
"Feature - GH Workflow Implementation "
"Feature/impl gh workflow"
"Feature/gh actions"

Android App

Lately, I’ve also taken to use the Github App on my phone to quick add new ideas

Clicking the “+” button on the upper right, then selecting my GH Repo lets me add an issue

/content/images/2021/12/gh-part3-58.jpg

And I can just quick jot my thoughts and save

/content/images/2021/12/gh-part3-59.jpg

Datadog integration

We enjoy the native service hooks in Azure DevOps with Datadog today. How can we track change in a similar way with Github?

Ideally, we would use a Kubernetes Secret with our API key like

$ cat ddjekyllsecret.yaml
---
apiVersion: v1
kind: Secret
metadata:
  name: ddjekyll
type: Opaque
data:
  DDAPIKEY: asdfasdfasdfasdfasdfsadf=

$ kubectl apply -f ddjekyllsecret.yaml
secret/ddjekyll created

$ diff -c my-jekyllrunner-deployment.yaml my-jekyllrunner-deployment.yaml.bak
*** my-jekyllrunner-deployment.yaml     2021-12-08 18:04:12.289615800 -0600
--- my-jekyllrunner-deployment.yaml.bak 2021-12-08 18:02:21.979615800 -0600
***************
*** 31,41 ****
            secretKeyRef:
              key: PASSWORD
              name: awsjekyll
-       - name: DATADOG_API_KEY
-         valueFrom:
-           secretKeyRef:
-             key: DDAPIKEY
-             name: ddjekyll
        image: harbor.freshbrewed.science/freshbrewedprivate/myghrunner:1.1.3
        imagePullPolicy: IfNotPresent
        imagePullSecrets:
--- 31,36 ----

$ kubectl apply -f my-jekyllrunner-deployment.yaml
runnerdeployment.actions.summerwind.dev/my-jekyllrunner-deployment configured

However, I tested a few patterns to try and leverage the ENV var in the api-key field, but in the end i had to use a Github project secret

/content/images/2021/12/gh-part3-60.png

And using it in the Github Actions Workflow:

Note: some DD steps are for Metrics and some for Events

$ cat .github/workflows/pr-final.yml
name: PR And Main Build
on:
  push:
    branches:
      - main
  pull_request:
jobs:
  build_deploy_test:
    runs-on: self-hosted
    if: github.ref != 'refs/heads/main'
    steps:
      - name: Check out repository code
        uses: actions/checkout@v2
      - name: bundle install
        run: |
          gem install jekyll bundler
          bundle install --retry=3 --jobs=4
      - name: build jekyll
        run: |
          bundle exec jekyll build
      - name: list files
        run: |
            aws s3 cp --recursive ./_site s3://freshbrewed-test --acl public-read
      - name: Build count
        uses: masci/datadog@v1
        with:
          api-key: $
          metrics: |
            - type: "count"
              name: "test.runs.count"
              value: 1.0
              host: $
              tags:
                - "project:$"
                - "branch:$"
  build_deploy_final:
    runs-on: self-hosted
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Check out repository code
        uses: actions/checkout@v2
      - name: bundle install
        run: |
          gem install jekyll bundler
          bundle install --retry=3 --jobs=4
      - name: build jekyll
        run: |
          bundle exec jekyll build
      - name: copy files to final
        run: |
            aws s3 cp --recursive --dryrun ./_site s3://freshbrewed.science --acl public-read
      - name: cloudfront invalidation
        run: |
            aws cloudfront create-invalidation --distribution-id E3U2HCN2ZRTBZN --paths "/index.html"
      - name: Build count
        uses: masci/datadog@v1
        with:
          api-key: $
          metrics: |
            - type: "count"
              name: "test.runs.count"
              value: 1.0
              host: $
              tags:
                - "project:$"
                - "branch:$"
  dd_tracking:
    runs-on: self-hosted
    steps:
      - name: Datadog-Fail
        if: failure()
        uses: masci/datadog@v1
        with:
          api-key: $
          events: |
            - title: "Passed building jekyll"
              text: "Branch $ failed to build"
              alert_type: "error"
              host: $
              tags:
                - "project:$"
      - name: Datadog-Pass
        uses: masci/datadog@v1
        with:
          api-key: $
          events: |
            - title: "Passed building jekyll"
              text: "Branch $ passed build"
              alert_type: "info"
              host: $
              tags:
                - "project:$"

After running it a few times, we see results

/content/images/2021/12/gh-part3-15.png

Using GH Datadog Integration

Datadog now as a new “CI” area for Continuous Integration tracking across DevOps toolsets.

/content/images/2021/12/gh-part3-16.png

We have to install a new “App”

/content/images/2021/12/gh-part3-17.png

And link to our Repo or Repos.

In my case, as a consequence of my Github identity being in Corporate systems, I had to narrowly focus my CI integration:

/content/images/2021/12/gh-part3-18.png

We can now see our results in the CI section

/content/images/2021/12/gh-part3-19.png

Digging into a Pipeline (Workflow), we can see details such as the stages (Jobs) that are performed and their duration

/content/images/2021/12/gh-part3-20.png

Or look a history of all our stages (jobs) chronologically:

/content/images/2021/12/gh-part3-21.png

Datadog Events for CI vs CI tracking

CI Tracking offers some great reporting and Dashboards. It’s easy to dig into Pipeline performance and issues.

But there is a consequence (other than cost).

There is no way, at present, to create a Monitor on CI Events. For me, the largest value of Datadog is Monitoring and Alerting.

I can use event tracking to create a monitor on events('tags:event_type:build priority:all failed sources:azuredevops').rollup('count').last('5m') > 2

/content/images/2021/12/gh-part3-22.png

This can email me on failures

/content/images/2021/12/gh-part3-24.png

As well as update Teams / Slack.

/content/images/2021/12/gh-part3-23.png

And while it’s not exactly simple, I can bring events from Github Actions and AzDO together in a Dashboard

/content/images/2021/12/gh-part3-25.png

And lest I forget, I can create a monitor for Github Workflows as I did for AzDO Pipelines:

/content/images/2021/12/gh-part3-26.png

Once I set conditions and notifications

/content/images/2021/12/gh-part3-27.png

Tracking Failures

As a final note, to properly track errors I’ll want a job that conditionally fails if either prior job fails then sends that error to Datadog as an event

  run-if-failed:
    runs-on: ubuntu-latest
    needs: [build_deploy_test, build_deploy_final]
    if: always() && (needs.build_deploy_test.result == 'failure' || needs.build_deploy_final.result == 'failure')
    steps:
      - name: Datadog-Fail
        uses: masci/datadog@v1
        with:
          api-key: $
          events: |
            - title: "Failed building jekyll"
              text: "Branch $ failed to build"
              alert_type: "error"
              host: $
              tags:
                - "project:$"

In fact, if we want to tailor our metrics and track failures as a metric too, we would update our workflow to look as such:

name: PR And Main Build
on:
  push:
    branches:
      - main
  pull_request:
jobs:
  build_deploy_test:
    runs-on: self-hosted
    if: github.ref != 'refs/heads/main'
    steps:
      - name: Check out repository code
        uses: actions/checkout@v2
      - name: bundle install
        run: |
          gem install jekyll bundler
          bundle install --retry=3 --jobs=4
      - name: build jekyll
        run: |
          bundle exec jekyll build
      - name: list files
        run: |
            aws s3 cp --recursive ./_site s3://freshbrewed-test --acl public-read
      - name: Build count
        uses: masci/datadog@v1
        with:
          api-key: $
          metrics: |
            - type: "count"
              name: "prfinal.runs.count"
              value: 1.0
              host: $
              tags:
                - "project:$"
                - "branch:$"
      - name: Datadog-Pass
        uses: masci/datadog@v1
        with:
          api-key: $
          events: |
            - title: "Passed building jekyll"
              text: "Branch $ passed build"
              alert_type: "info"
              host: $
              tags:
                - "project:$"
  build_deploy_final:
    runs-on: self-hosted
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Check out repository code
        uses: actions/checkout@v2
      - name: bundle install
        run: |
          gem install jekyll bundler
          bundle install --retry=3 --jobs=4
      - name: build jekyll
        run: |
          bundle exec jekyll build
      - name: copy files to final
        run: |
            aws s3 cp --recursive --dryrun ./_site s3://freshbrewed.science --acl public-read
      - name: cloudfront invalidation
        run: |
            aws cloudfront create-invalidation --distribution-id E3U2HCN2ZRTBZN --paths "/index.html"
      - name: Build count
        uses: masci/datadog@v1
        with:
          api-key: $
          metrics: |
            - type: "count"
              name: "prfinal.runs.count"
              value: 1.0
              host: $
              tags:
                - "project:$"
                - "branch:$"
      - name: Datadog-Pass
        uses: masci/datadog@v1
        with:
          api-key: $
          events: |
            - title: "Passed building jekyll"
              text: "Branch $ passed build"
              alert_type: "info"
              host: $
              tags:
                - "project:$"
  run-if-failed:
    runs-on: ubuntu-latest
    needs: [build_deploy_test, build_deploy_final]
    if: always() && (needs.build_deploy_test.result == 'failure' || needs.build_deploy_final.result == 'failure')
    steps:
      - name: Datadog-Fail
        uses: masci/datadog@v1
        with:
          api-key: $
          events: |
            - title: "Failed building jekyll"
              text: "Branch $ failed to build"
              alert_type: "error"
              host: $
              tags:
                - "project:$"
      - name: Fail count
        uses: masci/datadog@v1
        with:
          api-key: $
          metrics: |
            - type: "count"
              name: "prfinal.fails.count"
              value: 1.0
              host: $
              tags:
                - "project:$"
                - "branch:$"

Verification

We can see how that works:

/content/images/2021/12/gh-part3-28.png

When run, we can immediately see the metric in Metrics Explorer:

/content/images/2021/12/gh-part3-29.png

Now let’s intentionally fail the process a few times to see what that looks like.

I’ll add an intentional fail on the build job:

jobs:
  build_deploy_test:
    runs-on: self-hosted
    if: github.ref != 'refs/heads/main'
    steps:
      - name: Check out repository code
        uses: actions/checkout@v2
      - name: bundle install
        run: |
          gem install jekyll bundler
          bundle install --retry=3 --jobs=4
          echo FAIL
          exit 1

We can see it properly fails and triggers the trailing ‘if fail’ job

/content/images/2021/12/gh-part3-30.png

I failed it again and we can see that both instances of fail metrics are captured in Datadog; which we can see in the Metrics Explorer

/content/images/2021/12/gh-part3-31.png

This then triggers the Monitor that updates Pagerduty which is also tied to Slack

/content/images/2021/12/gh-part3-32.png

And since this is Pagerduty, I see that alert anywhere I’ve set up my notifications

/content/images/2021/12/gh-part3-33.jpg

Of course, from the monitor itself we get a notification

/content/images/2021/12/gh-part3-34.png

Over Alerting is Worse than No Alerting

One word of caution on this. It is easy to over-alert by integrating too many overlapping systems. If we pick “all the options” and send emails, Datadog alerts, then tie to a pager system like PagerDuty, our support staff can be buried by a mountain of alerts anytime something goes amiss

/content/images/2021/12/gh-part3-35.png

The result is always that if we have too much notification it ceases to be Information and becomes Noise. And Noise always is invariably ignored.

The right solution is to limit alerting to narrow audiences who can action systems.

Escalation Paths

Let’s consider an example to illustrate this point. Something that comes up in my professional life often is being CC’ed on Revision Control System notifications (Github/AzDO Repos). In the last several corporations of which I’ve been in the employ, this has meant all revision control system notifications are filtered to trash.

This means wasted bandwidth, storage and bits and a false sense of “well the DevOps folks are CC’ed” on changes.

The better approach is to limit notifications to just the audience that needs to be aware - that should be approvers on change.

In Azure Repos we would use Global Review Policies to, perhaps, require a Data Team (e.g. DatabasePeople) to review any database type changes:

/content/images/2021/12/gh-part3-36.png

In Github, if you are in a Paid Tier (not available in Free accounts), you can use CODEOWNERS files in private repositories for policies and require a minimum set:

/content/images/2021/12/gh-part3-37.png

There are solutions for using the Code Owners files using the codeowners-checker GEM which could be integrated into a PR build.

I should note that you can leverage the CODEOWNERS and PR checks in Public Repositories in the free tier:

/content/images/2021/12/gh-part3-38.png

CODEOWNERS Demo

Let’s make a quick public repo and clone it down

$ git clone https://github.com/idjohnson/coTest.git
Cloning into 'coTest'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 593 bytes | 593.00 KiB/s, done.

Next, we make a CODEOWNERS file either at the root of the repo or in a .github folder (I find the latter cleaner).

$ cd coTest/
$ mkdir .github
$ vi .github/CODEOWNERS
$ cat .github/CODEOWNERS
# Default Owners

@idjohnson

*.txt tristan.cormac.moriarity@gmail.com

Now add and push it.

$ git add .github/
$ git commit -m "new codeowners file"
[main 6c510e2] new codeowners file
 1 file changed, 7 insertions(+)
 create mode 100644 .github/CODEOWNERS

$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 16 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 406 bytes | 406.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To https://github.com/idjohnson/coTest.git
   dfb5309..6c510e2  main -> main

Next, a different user in Github viewing the repo decides to edit a file:

/content/images/2021/12/gh-part3-40.png

They do not get to commit directly, rather “propose a change”

/content/images/2021/12/gh-part3-41.png

Once proposed (Creating a PR branch in their own namespace), they can “Create a pull request”

/content/images/2021/12/gh-part3-42.png

But as they are not codeowners, they have to wait on others to action the PR.

/content/images/2021/12/gh-part3-43.png

As you recall, I made this user able to update *.txt files.. so what happens when we add one of those.

Our other user (Tristan) will now create a new file

/content/images/2021/12/gh-part3-44.png

and name it test.txt

/content/images/2021/12/gh-part3-45.png

Then propose a change

/content/images/2021/12/gh-part3-46.png

which prompts them to create a PR

/content/images/2021/12/gh-part3-47.png

/content/images/2021/12/gh-part3-48.png

But why can they not action that PR?

This is because the CODEOWNERS file requires those reviewers to have write access to the repo.

We are only blocked now because we neglected to add this user as a contributor with write permissions per the docs

Let’s correct that..

In Manage access, we need to add a “collaborator”

/content/images/2021/12/gh-part3-49.png

Now select the user and send the invite

/content/images/2021/12/gh-part3-50.png

And of course our user has to accept

/content/images/2021/12/gh-part3-51.png

We can see our user now can merge PRs regardless of reviewer state:

/content/images/2021/12/gh-part3-52.png

Now our owner will edit the text file.

/content/images/2021/12/gh-part3-53.png

And we see indeed our Tristan user was added as review automatically.

/content/images/2021/12/gh-part3-54.png

I still needed to click the request button but we can now see that the PR is asking for the person to review:

/content/images/2021/12/gh-part3-55.png

And our reviewer gets emails about being added:

/content/images/2021/12/gh-part3-56.png

That user can now review and merge

/content/images/2021/12/gh-part3-57.png

On Notifications / Too Many Optional Reviewers

Getting spammed on updates and reviews might be okay in some cases.

However, in general, I find for the purpose of notification, encouraging users to subscribe to repositories they care about is a far better approach than marking as optional reviewers on PRs.

/content/images/2021/12/gh-part3-39.png

Summary

In this blog post we covered Project setup in Github with Issue creation and tracking. We compared to Azure Devops and then explored automated reviewers by way of CODEOWNERS vs Branch Policies in Azure DevOps. We walked through a Demo of CODEOWNERS using a Public repo.

Lastly, we focused on Datadog integration, how to use CI tracking as well as tie in Events and Metrics directly in our Github workflows. By way of example, we used a monitor to escalate alerts to Slack and PagerDuty and wrapped by discussing the guiding principles of notifications and escalations in revision control systems.

Github has a lot to offer, even in the free tier. Arguably it takes a bit more work to constrain change outside of the paid levels in comparison to a free tier of Azure DevOps. But this may be worth it as the maturity of Github Actions continues to grow while Azure Pipelines has not had the same level of adoption or updates.

github actions kubernetes

Have something to add? Feedback? Try our new forums

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes