Tweeting New GitHub Pages Posts from GitHub Actions
For the last few years, I hosted my blog on the Ghost platform. It was a fast, Node-powered CMS, which allowed for stupid-simple publishing: I could get in, write, and get out. However, I was looking at another annual bill for $220 and I wanted to find a better (and cheaper) way. I knew of the myriad of static site generators out there today. I eventually landed on Jekyll + GitHub Pages. A month in, I’m happy that GitHub Pages gives me the flexibility to customize as I wanted, but also the simplicity. I can push a markdown file to GitHub, and then deploy to daveabrock.com automatically. All for just the cost of my domain name ($9 a year)!
After I publish my posts, I typically post them to Twitter. Ghost had a setting to do this. Could I do this from GitHub Pages? Not easily, it seemed, without leveraging external services. I could maybe toy with an RSS feed trigger from IFTTT or build something with Azure Logic Apps. Of course, it’s a silly thing to obsess over but why should I manually do something repeatedly?
A thought occurred to me: if I’m using GitHub, shouldn’t I be able to use their pipeline? I knew that I could leverage GitHub Actions in my repo, which the docs say allow me to “discover, create, and share actions to perform any job you’d like, including CI/CD, and combine actions in a completely customized workflow.” I would love to make this a deployment step.
Alas, being forced to manually tweet about a new post a few times a month wasn’t one of my life’s biggest regrets—but if I could spend a few hours learning about GitHub Actions while saving a few minutes every month, why not?
This post is about learning. Sure, you could look at my 16-line YAML file say, “looks pretty simple” and maybe it is, in retrospect. But when you try something new, you stumble and you learn—and that’s what this post is about.
This post contains the following content.
- The perfect world, and what I can do now
- What you need before you get started
- Add Twitter API secrets to GitHub
- Create your first GitHub Action
- How to get just the commit message from Git
- Passing commit message to my GitHub action
- Wrapping up
The perfect world, and what I can do now
Without knowing much about GitHub Actions, here’s the workflow I envisioned:
- Every time I write a post, push my markdown file (and associated files, like images) to my repository.
- Run a GitHub Actions workflow step from that push
- In that step, get the post title and path to the post
- Send a tweet with this information
As you can imagine, it was the third step that took some thought. In a perfect world, here’s how I would grab the post and URL information:
- Somehow, grab the Git commit data and parse the committed files
- Assuming I’ll only be pushing one markdown file, get the title metadata which is sitting inside the file
- Get path, which more or less is the filename (would just have the replace the
.markdown
extension with.html
) - Pass that string to the Send Tweet Action
Did I mention I didn’t want to write any additional code or scripts, or spend more than a few hours on this? It was time to reset my expectations and think a little smaller. Thanks to some input from my friend Isaac Levin from Microsoft—on Twitter, naturally—I decided to just add the title and URL to my Git commit message. Then, I could just pass down the commit message to the Twitter action. This was manageable.
Let’s get started.
What you need before you get started
If you’re playing along at home, you need to do the following before proceeding with this post:
- Register for a Twitter developer account. It wouldn’t be a terrible idea to set up a test account first to avoid spamming your followers
- Once the registration is approved, head over to developer.twitter.com/apps and create a Twitter application. You’ll need to do this so the Send Tweet Action can call the Twitter API on your behalf
- A GitHub repository (this demo works for any push, but the obvious use case is a GitHub Pages site)
Add Twitter API secrets to GitHub
We will be using the Send Tweet Action, developed by GitHub’s Edward Thomson. This action handles connecting to Twitter API to send the tweet. We need to provide it the Twitter consumer API key, Twitter consumer API secret, Twitter access token, and Twitter access token secret.
To retrieve those, go to developer.twitter.com/apps, click Details
next to the application you created, then the Keys and tokens
link.
You need to add those to GitHub as encrypted secrets. From GitHub, go to Settings > Secrets. I recommend calling them TWITTER_CONSUMER_API_KEY
, TWITTER_CONSUMER_API_SECRET
, TWITTER_ACCESS_TOKEN
, and TWITTER_ACCESS_TOKEN_SECRET
, so you can easily follow the code in this post. For details on storing encrypted secrets in GitHub, read this GitHub article.
Create your first GitHub Action
To get started, you’ll need to set up a GitHub Action. To do that, click the Actions
link at the top of your repository, right next to Pull Requests
. From there, you’ll see the power of GitHub Actions—there are so many CI workflows and automation processes to choose!
For us, though, we’ll use Simple Workflow.
In that pane, click Set up this workflow.
Once you do that, you will see that you are now editing a blank.yml
file (which you can rename), sitting in a .github/workflows
directory. We’ll be updating this file.
Let’s send a tweet! Replace the contents in blank.yml
with this, right in GitHub, and commit the file. (We will explain particulars in a second.)
name: Send a Tweet
on: [push]
jobs:
tweet:
runs-on: ubuntu-latest
steps:
- uses: ethomson/send-tweet-action@v1
with:
status: "NEW POST!"
consumer-key: ${% raw %}{{ secrets.TWITTER_CONSUMER_API_KEY }}{% endraw %}
consumer-secret: ${% raw %}{{ secrets.TWITTER_CONSUMER_API_SECRET }}{% endraw %}
access-token: ${% raw %}{{ secrets.TWITTER_ACCESS_TOKEN }}{% endraw %}
access-token-secret: ${% raw %}{{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}{% endraw %}
After you commit the file, you can head over to the Actions
page in GitHub to monitor the status.
With any luck, you should see a tweet that says “NEW POST!” Let’s break down what’s happening. (You can also review the docs for details on GitHub Actions workflow syntax.)
name
- the name of your workflow, which is displayed on the actions pageon
- the event that triggers the workflow. I includedpush
but you can also dopull_request
or other optionsjobs
- a workflow comprises one or more jobsruns-on
- the type of machine to run the job on (required)steps
- one of a sequence of tasks in a jobuses
- selects an action to run. In our case, we are using the Send Tweet Action from the GitHub Marketplacewith
- the input that the action requires. For this action, thestatus
is the tweet text, and the rest of the fields reference the secrets you created earlier.
So, this is great! However, your followers aren’t mind readers. Tweeting “NEW POST!” doesn’t do much. We now have to figure out how to tweet the commit message.
How to get just the commit message from Git
To get the latest commit from Git, I know I can use git log 1
. Here’s what I get back:
commit cc789c6bd3032e0d28111b2515f3931cb5b95e4b (HEAD -> master)
Merge: e5b2597 a07f6b5
Author: Dave
Date: Sun Apr 19 08:05:02 2020 -0500
Merge branch 'master' of https://github.com/daveabrock/daveabrock.github.io
I already see two problems: (1) this is a merge commit message, and (2) I just want the message.
How about git log 1 --no-merges
?
commit cc789c6bd3032e0d28111b2515f3931cb5b95e4b (HEAD -> master)
Author: Dave
Date: Sun Apr 19 08:05:02 2020 -0500
my commit
Better! But I just want to see my commit.
Luckily, Git has a --pretty
flag. I can pass --pretty=%B
, where %B
is the raw body of the commit message. So now, let’s pass git log --no-merges -1 --pretty=%B
. And what do we get?
my commit
Victory! (Or you could have googled it, but what fun is that?)
Passing commit message to my GitHub action
So now, I know how to do things: how to tweet when I push and how to get my commit message. How do we connect them? From my blank.yml
file, I want a way to run git log
and save the output, then pass the output to the action’s status
field.
From what I could see, the checkout
action allows me to fetch commit details. So, I can do something like this:
name: Send a Tweet
on: [push]
jobs:
tweet:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- id: log
run: echo "$(git log --no-merges -1 --pretty=%B)"
# rest of file removed for brevity
If I look at the output from the Actions page, everything looks great. Now, how can I store this in a variable? Well, I was initially reading about a steps.logs.outputs
object. If, for example, I changed my run command to echo "::set-output name=message::$(git log --no-merges -1 --pretty=%B)"
I could access the value from using steps.log.outputs.message
. Great, let’s try it! Here’s the full file. What do you think will happen?
name: Send a Tweet
on: [push]
jobs:
tweet:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- id: log
run: echo "::set-output name=message::$(git log --no-merges -1 --pretty=%B)"
- uses: ethomson/send-tweet-action@v1
with:
status: "NEW POST: ${% raw %}{{ steps.log.output.message }}{% endraw %}"
consumer-key: ${% raw %}{{ secrets.TWITTER_CONSUMER_API_KEY }}{% endraw %}
consumer-secret: ${% raw %}{{ secrets.TWITTER_CONSUMER_API_SECRET }}{% endraw %}
access-token: ${% raw %}{{ secrets.TWITTER_ACCESS_TOKEN }}{% endraw %}
access-token-secret: ${% raw %}{{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}{% endraw %}
This results in a NEW POST: tweet, sadly, with no commit data. This does not persist across actions or steps (in retrospect, the reason for it being in steps
). Luckily, GitHub Actions has environment variables, which come to our rescue.
While I was on the right track with my run
statement, I should have used set-env
instead. This injects the value into an env
object that GitHub Actions uses for environmental variables. So: if I say set-env name=POST_COMMIT_MESSAGE
I could access it later using ${% raw %}{{ env.POST_COMMIT_MESSAGE }}{% endraw %}
. Here is the updated file.
name: Send a Tweet
on: [push]
jobs:
tweet:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- id: log
run: echo "::set-env name=POST_COMMIT_MESSAGE::$(git log --no-merges -1 --pretty=%B)"
- uses: ethomson/send-tweet-action@v1
with:
status: "NEW POST: ${% raw %}{{ steps.log.output.message }}{% endraw %}"
consumer-key: ${% raw %}{{ secrets.TWITTER_CONSUMER_API_KEY }}{% endraw %}
consumer-secret: ${% raw %}{{ secrets.TWITTER_CONSUMER_API_SECRET }}{% endraw %}
access-token: ${% raw %}{{ secrets.TWITTER_ACCESS_TOKEN }}{% endraw %}
access-token-secret: ${% raw %}{{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}{% endraw %}
It works! So now, in the future, if I push a commit message to master
in the format <Post title> <url>
it will push to Twitter right away. (I can now customize based on PR or other policies, as well.)
Wrapping up
I hope you enjoyed reading this post as much as I did writing it. This was my first foray into this, so if you know of a better and simpler way to do this, leave a comment below (or on Twitter, obviously).