Over the past decade or so I’ve developed a strong allergy to typing commands after having already typed other commands. I’d rather have computers react to me, and do what needs to be done whenever they see telltale signs that I want to see change in the internet.
I call this “declarative automation.”
For years, I used that old UNIX standby, make
, to roll up all of the “automation” for things like deployments, release engineering, and test execution. The uniformity of running make test
and make release
freed up my brain to tackle other, more pressing problems.
Nowadays, I live my life in Git. Both professionally and personally, that means GitHub.
What is “GitHub Actions”?
GitHub Actions is a continuous integration / continuous delivery (CI/CD) automation solution that is built right into GitHub itself. If you’re hosting public repositories on GitHub currently, you can take advantage of Actions today!
With GitHub Actions, you as the repository owner string together one or more “actions” — small bits of self-contained functionality that perform some meaningful task. For example, there are Actions that:
- Publish an npm module,
- Compile Rust code into static binaries for use in Alpine Linux,
- Scan OCI Images for vulnerabilities,
- Upload Code Coverage reports to a number of shared services,
- Or even deploy your stuff via Amazon CloudFormation.
There’s a whole marketplace of existing Actions that you can make use of. You can even write your own!
There’s even an action to push an app to a Cloud Foundry. That’s what we’re going to play with today.
Getting Started
Before you can play along at home, you’ll need the following:
- A GitHub account – it’s free!
- A Cloud Foundry instance to deploy to. Sadly, the landscape of public Cloud Foundry offerings is getting smaller by the day.
- We’re going to need an application to deploy, and that application’s code needs to sit in a GitHub repository. We’re going to use a small demo-worthy “Welcome to CF” application. It looks a little something like this:
We’ll start by forking this repository on GitHub; that will give you a copy of the codebase that you can modify and commit against. Our workflow is going to be keyed on pushing commits to origin/main, so that is going to be important.
If you wanted to manually deploy this application, you would clone the repository, target your Cloud Foundry by its API, and issue a cf push
:
Taking Action – Let’s Automate This!
Cloud Foundry smartly requires authentication before it will allow you to push an application to it. Our GitHub Actions Workflow (which we’ll discuss in more detail in the next section) knows this, and relies on external configuration, via GitHub Secrets to securely access those authentication credentials.
A GitHub Secret is a potentially sensitive value that you give to the GitHub platform, and it promises to only hand out on an as-needed basis to appropriate workflows. Secrets can be managed at both per-repository and per-organization levels. For our purposes, we’re going to stick with defining the secrets we need on the repository level. To do so, click the “Settings” tab on the repository overview, and go to “Secrets”, on the sidebar.
Our cf push workflow needs the following secrets to be defined:
Secret | Description |
---|---|
CF_API | The full URL of the Cloud Foundry API, complete with the scheme. |
CF_ORG | The name of the Cloud Foundry organization you want to deploy to. |
CF_SPACE | The name of the Cloud Foundry space you want to deploy to. |
CF_USERNAME | The username to authenticate to Cloud Foundry with. |
CF_PASSWORD | The password to use for Cloud Foundry authentication. |
In the real world, verify your certificates!
For each of these, create a Secret with the given name and the correct value.
When you’re all done, you should see something that looks like this:
It’s worth noting that you can’t view these secrets; only update them. Yay, security!
With our secrets defined and ready to go, we can get started deploying this application automatically. Head over to the “Actions” tab, so we can enable workflows:
When you fork a repository that has GitHub Action Workflows defined, GitHub does the prudent thing and pauses those workflows. This prevents malicious code from executing and (for example) trying to steal secrets you may have configured. Before we can use these workflows, then, we must unpause them.
Once unpaused, you’ll see the defined workflow, named “Deploy”. This workflow is configured to run whenever new commits are pushed to the main branch on GitHub. Since we haven’t pushed any commits yet, the workflow isn’t doing anything.
Let’s remedy that by making a completely innocuous change. Open up the README.md using the GitHub web editor and add a blank line. This will create a commit, without having to bother with cloning the repo, opening an editor, or running any git
commands!
Once you click “Commit changes”, and GitHub detects the update, the machinery of GitHub Actions should spring to life, and kick off a deployment task. While it’s running, you’ll see a small yellow dot next to the commit ID in the GitHub web interface:
Clicking that takes you into the Actions UI, where you can follow along with the steps in our deployment workflow.
That’s it! We are deployed. If you copy the application URL from the cf push
output, you should be able to visit your newly deployed application. All without having to touch the CF CLI.
Workflow In-Depth
Clearly, GitHub Actions has a lot of potential, especially for public and Open Source projects. Now that we’ve seen what it can do, let’s dig a little deeper into the how.
Workflows live in YAML files in your code repository, under the ./github/workflows
directory. What you name those YAML files is mostly up to you, but they should end in .yml
, and not live under sub-directories. Each workflow gets its own file.
Here’s the workflow file we’re using:
name: Deploy to Cloud Foundry
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/[email protected]
- uses: jhunt/[email protected]
with:
api: ${{ secrets.CF_API }}
org: ${{ secrets.CF_ORG }}
space: ${{ secrets.CF_SPACE }}
username: ${{ secrets.CF_USERNAME }}
password: ${{ secrets.CF_PASSWORD }}
manifest: manifest.yml
validate: false
As we saw, this workflow is triggered by commits being pushed to the main
branch. That’s the on:
key up near the top of the YAML file. You can trigger GitHub Actions based on a variety of different events firing inside of GitHub:
- whenever a Pull Request is opened,
- … or a PR Review is submitted,
- … or an Issue is created,
- … at a specific time of day,
- … or even when someone creates a new GitHub Wiki page!
The real bulk of the automation exists under the jobs:
key – here we see that our job will run on the latest stable version of Ubuntu Linux (18.04). We could also run on Windows, or even macOS, if we wanted. The steps:
define the specific actions we want to take, and in what order. We’re using two actions: checkout and cf-push.
A workflow can consist of more than one job. All jobs will be run in parallel, allowing you to pursue independent streams of work in a shorter time frame. Steps inside of a job are executed sequentially, one at a time.
When you start using GitHub Actions, you’ll become quite familiar with the actions/[email protected]
action – that’s how you clone down a copy of the git repository the rest of your workflow will operate on. Except in some exceptional circumstances, your workflows are going to need that to do useful stuff.
Our second step bears further examination.
- uses: jhunt/[email protected]
with:
api: ${{ secrets.CF_API }}
org: ${{ secrets.CF_ORG }}
space: ${{ secrets.CF_SPACE }}
username: ${{ secrets.CF_USERNAME }}
password: ${{ secrets.CF_PASSWORD }}
manifest: manifest.yml
validate: false
We’re using a custom GitHub action, called jhunt/[email protected]
. This is just a specially laid-out GitHub repository, which you can find here, that implements the idea of pushing an application to Cloud Foundry. To do that, it needs a set of inputs, which we supply under the with:
key. Two of those inputs are hard-coded: the manifest and the TLS validation.
The other parameters are mapped to GitHub Secrets using the ${{ secrets.XYZ }}
notation. The ${{ ... }}
syntax tells the GitHub Actions machinery to go off and find some other value, here the secret we defined earlier, and replace those dynamically, /each time the workflow runs/. I made almost all of these inputs into Secrets specifically so that you could fork the repo and not have to change any real code. In a real workflow definition, at least half of these inputs would be hard-coded into the YAML.
If you want to see how the jhunt/cf-push
GitHub action is put together, you can review the source code over on GitHub; it’s open source. Interestingly, you can also see what makes the actions/checkout
Action tick too – actions
is just the name of a GitHub organization run by GitHub themselves!
What Else Can We Do?
I’ll leave you with some tantalizing ideas for the future; of things you can do with GitHub actions to make the most use out of this excellent bit of hosted CI/CD.
- Run unit tests on every commit.
- Run integration tests on every pull request.
- Redeploy your Cloud Foundry static website whenever content changes.
- Crank out BOSH release tarballs via GitHub releases.
- Get notified in Slack every time someone stars your F/OSS repos.
- Test the viability of BOSH releases.
- Make GitHub releases, complete with binary assets.
Until next time, Happy Hacking!