In this final article of our three-part series, we will be sending you off with a guide on standing up Continuous Integration for Salesforce with Github Actions. In the first article, we talked about the value CI/CD can bring to your organization. In the second article we dove into the tools and event structure we’re looking into on Github, our preferred source repository. While concluding this mini-series, we will depart with providing the steps to standing up your own pipeline with Github Actions.
Salesforce Continuous Integration
Workflow Setup
The basic idea for continuous integration is to ensure that any changes you or your team make to the code base are tested. This can be done by setting up a workflow that will run a validation deployment on a pre-authorized salesforce org (a developer org would work fine).
The first step to this is to write out a workflow file that can provide GitHub with the “when” and “how” to run a validation.
When
In the previous article, we talked about some of the different types of events that GitHub actions provide. In order to ensure continuous integration of your changes with the rest of the codebase, the ideal scenario would be to test your code prior to merging, or in other words when you open a pull request. Some of these pull request events are:
- opened – (PR is opened)
- synchronize – (branch pushed to)
- reopened – (branch re-opened)
- ready_for_review – (PR marked as “ready for review”)
In your workflow.yml file, this would look like this:
name: Example CI/CD Github Action
on:
pull_request:
branches: [ main ]
types: [ opened, synchronize, reopened, ready_for_review ]
paths:
- 'force-app/main/default/**'
...
The last 2 lines ensure that the workflow will only run when a file in your pull request is within the force-app/main/default directory.
How
The “how” can be represented as the jobs section of the workflow, where you specify the exact steps that need to be taken. In this example, we only care about having one job with multiple steps.
When defining a job, it’s important to choose the environment that this will run on, for our uses, we will choose ubuntu-latest. Another optional step is to say that this job will not occur when the pull request is in “draft” state. This makes use of the “ready_for_review” event stated above.
This will look something like the following:
...
jobs:
validate:
name: Validate
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
...
NOTE: All of the actions specified in the workflow are written in node, but it should be possible to get similar results using something like python, or even just bash scripts.
The basic steps are represented in the figure below.
Setup Node Environment
...
steps:
- name: Setup Node Environment
uses: actions/setup-node@v2-beta
with:
node-version: '12'
...
Checkout Local Repository
...
steps:
...
- name: Checkout Local Repo
uses: actions/checkout@v2
with:
fetch-depth: 0
...
Checkout Sub-Action Repositories
We use 2 sub-actions here which are responsible for the core features of this whole workflow. These 2 actions are the following:
- SFDX Authentication Action
- SFDX Validation Deployment Action
While both of these actions were custom written for our uses, we will go into more detail on those later in the article. There is a public repository on the GitHub actions workplace for the SFDX Authentication action (This is not provided by us, but can be useful to see a similar process that we use, use at your own discretion).
Both actions should show up differently in the workflow.yml, but would follow this similar pattern:
...
steps:
...
- name: Checkout Custom Action
uses: actions/checkout@v2
with:
repository: organization/action-repo
token: ${{ secrets.DEVOPS_TOKEN }}
ref: main
path: .github/actions/action-repo
...
NOTE: Replace action-repo and organization/action-repo with the name of the custom github action repository.
There are 3 main configuration options here which are important to point out:
- token
- ref
- path
The token is one of the more important pieces. GitHub provides the ability to configure 2 types of secret tokens, repository-based and organization-based. Org tokens are available across all projects within an organization, whereas the repository secrets are stored on a per-repository basis. In our case, the DEVOPS_TOKEN is stored on an organization level, and provides read access to repositories within the organization. Without this token we would be unable to access private repositories within the organization from our workflow.
The ref is stating which branch the action needs to be pulled from. In this case, we want main or master to be pulled from, in some cases you may want a dev or testing branch however.
The path provides a local directory (relative to the workflow’s repository root) of where this action should be pulled in. This happens only during the runtime of the workflow, and any files retrieved will not persist in your repository after the workflow exits.
Repeat this step for any custom actions needed in your workflow.
Authenticate Target Org
The previous steps were all configuration and setup, these following steps are the “meat and potatoes” of the whole workflow; Authorizing & Validating.
If you are using the example action provided above for SFDX CLI Authentication, this step may look a little different, but this is how we use our action to authorize an org.
...
steps:
...
- name: Install SFDX & Authorize Org
uses: .github/actions/rad-sfdx-cli-action
with:
sfdx-auth-url: ${{ secrets.AUTH_SECRET }}
...
It’s important to note the following pieces from this:
- uses
- sfdx-auth-url
The uses section must provide the same location that was specified in the path section from the previous checkout step. This is to provide the workflow with a path to a valid GitHub action.
The sfdx-auth-url is a custom input parameter to our rad-sfdx-cli-action, but does what the provided public repository does. This needs to be stored as a repository-level secret and serves as the “Authentication URL” for a specified validation org. This can be retrieved by authorizing a project in VSCode and in the terminal typing:
sfdx force:org:display -u ORG-ALIAS --verbose
This will result in the following output:
force://<clientId>:<clientSecret>:<refreshToken>@<instanceUrl>
After this step in the workflow executes, you are now authenticated with the specified validation org. The next step is to run the validation deployment.
Run Validation Deployment
For this step, we again use a custom action which tracks the validation status (number of tests running, intermediate status, etc) and creates comments on the PR. This can be done in any number of ways and depends on the output needed. Github Actions themselves, when run on a pull request, can report as a fail/success based on the return value from the shell, so this process could run fine as a few lines of bash, but it depends on the scope that your project needs.
The basic command that needs to be executed is the following:
sfdx force:source:deploy --checkonly --testlevel "RunLocalTests" --json -p “${fileNames}”
fileNames is a comma-separated list of files that are included in the pull request. This can be retrieved by using something like a Get Diff Action or even by using the sfdx-git-delta package.
Our action comments look like the following:
(Failure)
(Success)
This is what this step looks like in our workflow:
...
steps:
...
- name: Validate Changes
uses: ./.github/actions/rad-ci-action
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
...
The uses parameter is similar to that as above, and needs to reflect the path from where the custom action was checked out.
The GITHUB_TOKEN parameter is provided by default to GitHub action workflows, and useful for performing basic tasks such as commenting on a PR, or updating the status. Read more about the GITHUB_TOKEN here.
That’s it!
Now that you have each piece of the workflow, this is what the resulting workflow file will look like:
name: Example CI/CD Github Action
on:
pull_request:
branches: [ main ]
types: [opened, synchronize, reopened, ready_for_review]
paths:
- 'force-app/main/default/**'
jobs:
validate:
name: Validate
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- name: Setup Node Environment
uses: actions/setup-node@v2-beta
with:
node-version: '12'
- name: Checkout Local Repo
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Checkout rad-sfdx-cli-action
uses: actions/checkout@v2
with:
repository: redargyle/rad-sfdx-cli-action
token: ${{ secrets.DEVOPS_TOKEN }}
ref: main
path: .github/actions/rad-sfdx-cli-action
- name: Checkout rad-ci-action
uses: actions/checkout@v2
with:
repository: redargyle/rad-ci-action
token: ${{ secrets.DEVOPS_TOKEN }}
ref: main
path: .github/actions/rad-ci-action
- name: Install SFDX & Authorize Org
uses: ./.github/actions/rad-sfdx-cli-action
with:
sfdx-auth-url: ${{ secrets.AUTH_SECRET }}
- name: Validate Changes
uses: ./.github/actions/rad-ci-action
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
That does it for our three part series on all things GitHub. If you missed the first two parts feel free to check out Part I and Part II on our website. We hope this was helpful for you and your business process. Please feel free to drop any questions below or contact us!