What are GitHub Actions
GitHub Actions is a Continuous Integration & Deployment platform provided by GitHub that can be used to deploy your code from one environment to another environment. You can create workflows and jobs to trigger the pipeline and deployment.
How To Setup CI/CD Using GitHub Actions for Salesforce
- Create
.github
a folder within the parent directory of your Git Repo - Create
workflow
folder within.github
folder - Create
github-actions-demo.yml
file withinworkflow
folder and use below sample YML code for the initial setup
name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v3
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
The above pipeline is a simple GitHub Action pipeline
Pipeline Explanation
name
: The name that will be displayed under the Actions Tab. This could be any meaningful word. Like Prod Pipeline, QA Pipeline, etcrun-name
: The title that will be displayed when the GitHub Action will runon
: These are the events when you wanted to execute your pipeline. For Example, you only wanted to execute the pipeline when the pull request is merged then the event will bepull_request
. To more all about the event Check the Official DocumentJobs
: This is the place where we define our all Jobs that will be executedruns-on
: This is the name of the runner where you wanted to run your pipeline. I have usedubuntu-latest
but you can use from the Available runners in GitHub Actionssteps
: These are the steps that we define within our Jobs. For Example, installing the SFDX, Authenticating with Salesforce, Running Apex Test, Deployment, &, etc
Prepare your Salesforce Environment for Github Action CI/CD
Authentication
As we will be using the SFDX Commands to deploy the code using GitHub Action CI/CD tool so we need to authenticate using JWT. Please Follow Salesforce Document to generate the Certificate and Create the Connection Application inside Salesforce
- Create an asset folder on the parent directory ( same level as the .github folder) of your git repo, we will use this in later steps
server.key
file using OpenSSL
Encrypt the Generate the Key & IV
Execute the below command within the folder where your server.key
the file is located to generate the KEY & IV, once generated then please take a note and store it in some place from where you can easily get
openssl enc -aes-256-cbc -k GITHUBACTIONS -P -md sha1 -nosalt
Encrypt the server.key file & generate server.key.enc file
Execute the below command within the folder where your server.key
the file is located to generate the encrypted file.
openssl enc -nosalt -aes-256-cbc -in server.key -out server.key.enc -base64 -K <KEY> -iv <IV>
Note: In the above command use your KEY & IV which you have generated in the previous step
Place server.key.enc file within the asset folder of your repo
Test #1
Now that we are done with the first step, let’s push this code to our GitHub and see the GitHub Action Running
Install SFDX CLI in the pipeline
Now, as we are done with the simple pipeline and we have also done with the steps for authentication with Salesforce! Let’s make modifications in our pipeline to add a job, here we will perform the steps related to Salesforce Deployment. The first step in this pipeline would be installing the SFDX and testing if the SFDX has been installed or not
In your pipeline yml file add the below code
build:
runs-on: ubuntu-latest
steps:
# Checkout the Source code from the latest commit
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install NPM
run: |
npm install
# Install the SFDX CLI using npm command
- name: Install the SFDX CLI
run: |
npm install sfdx-cli --global
sfdx force --help
If you are making the changes into GitHub directly, then commit the changes and see the magic. If you are making the changes in the local repo then you need to commit and push the changes to the remote branch.
Note: The indentation is very important in the pipeline. So you need to be very careful. You can use Online YML Validator to validate your YML file
Here is the yml
file after making the above changes
name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v3
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
build:
runs-on: ubuntu-latest
steps:
# Checkout the Source code from the latest commit
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install NPM
run: |
npm install
# Install the SFDX CLI using npm command
- name: Install the SFDX CLI
run: |
npm install sfdx-cli --global
sfdx force --help
Authenticate to Salesforce in Pipeline
In the above step, we have successfully installed the SFDX Pipeline the next step is to authenticate to Salesforce ORG so that we can perform the validation or deployment.
server.key.enc
file
Decrypt the - Remember we encrypted the
server.key
file at the initial steps and placed the outcome insideassets
folder - Decrypt the
server.key.enc
file to get theserver.key
at runtime to make sure that we have the private key to establish the connection with Salesforce using the JWT method. - Add one more step within
build
job to decrypt the key. use below command
- name: Decrypt the server.key.enc file & store inside assets folder
run: |
openssl enc -nosalt -aes-256-cbc -d -in server.key.enc -out server.key -base64 -K <YOUR_KEY_VALUE> -iv <YOUR_IV_VALUE>
Note:- Use your key & iv value that you generated at the very first step
Authenticate to Salesforce using Pipeline
After we have decrypted the server.key
in the previous and have got the key file that we need for authentication. Now, the time is to authenticate to Salesforce Username using JWT. Below is the command for authentication
sfdx force:auth:jwt:grant --clientid YOUR_CLIENT_ID --jwtkeyfile assets/server.key --username SALESFORCE_USERNAME --setdefaultdevhubusername -a HubOrg
Note
Replace YOUR_CLIENT_ID with your salesforce connected app consumer key Replace SALESFORCE_USERNAME with the salesforce deployment username
After making the changes, commit & push those changes to the remote branch and see the outcome! You must see the success message below
Validate the code base to Salesforce Org
Congratulations 🎉, You have successfully authenticated to Salesforce Org. Now, the last step that is remaining is validating the code base to Salesforce Org. To validate/deploy the code base uses the below sfdx command
sfdx force:source:deploy -p force-app -c -u HubOrg
Where
-p
path to source code-c
remove this if you want to deploy. -c is used to indicate that the code will be validated but not deployed-u
the target org username that is HubOrg as we have used HubOrg as an alias in the authentication command
If you want to do the direct deployment then remove -c
from the above sfdx command
WoooooHoooooo 🎉 You have successfully developed a simple GitHub Action Pipeline that validates the code against salesforce org every time a push is happening in the repo.
Here is the complete YML file for your reference
name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v3
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
build:
runs-on: ubuntu-latest
steps:
# Checkout the Source code from the latest commit
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install NPM
run: |
npm install
# Install the SFDX CLI using npm command
- name: Install the SFDX CLI
run: |
npm install sfdx-cli --global
sfdx force --help
- name: Decrypt the server.key.enc file & store inside assets folder
run: |
openssl enc -nosalt -aes-256-cbc -d -in assets/server.key.enc -out assets/server.key -base64 -K DECRYPTION_KEY -iv DECRYPTION_IV
- name: Authenticate Salesforce ORG
run: |
sfdx force:auth:jwt:grant --clientid HUB_CONSUMER_KEY --jwtkeyfile assets/server.key --username HUB_USER_NAME --setdefaultdevhubusername -a HubOrg
- name: Validate Source Code Against Salesforce ORG
run: |
sfdx force:source:deploy -p force-app -c -u HubOrg
Path Filtering in Github Action
In the current implementations anytime when the codebase is pushed to any branch then the pipeline is execting and because of this, we are validating the codebase even if there is not change in the code base. For example, if you change in the yml
the file then also the pipeline is executing however this should not happen.
So, let’s add path filtering in Github Action
To include the path filters, we need to use paths in on
events like push
given is the example for the same
on:
push:
paths:
- 'force-app/**'
Commit and publish the changes, this time you will notice that no action is executed.
You can use the same concept for other folders as well and for the other events like pull_request
Add Environments in GitHub Actions
Adding the environment in Github Action is very important because whenever you are making the changes to the codebase and pushing the changes the validation runs against the org. What if you wanted to deploy the code to different environments like Integration, QA, Staging, SIT, &, etc and this will be an obvious use case? You must be deploying the code to a different environment.
Steps to create an Environment in GitHub Actions
- Open the Repo where you wanted to create an environment
- Click on the Setting tab to open the settings
- Locate the Environment item from the left side
- Click on New Environment to create a New Environment
- Give a name & click on Configure Environment
Congratulations 🎉 you have created the environment. If you wanted to create more environment then follow the same steps
Configures Secrets in Github Action Environments
Because we are using the values directly in the yml
there are chances that some intruder can access the information and get unauthorized access to our Salesforce environment it is always best practice to create secrets and store all the sensitive information in secrets. For Example, username, key file, client id, login URL &, etc.
Also as the requirement is to deploy the code to various environments and the credentials and URL will be different for each environment.
- While you are on the environment page
- Click on the
add secret
button underEnvironment secrets
section - and add the following secrets for your environment
- DECRYPTION_KEY is the value of the Key file to decrypt the server.key.inc file
- DECRYPTION_IV is the value of the IV file to decrypt the server.key.inc file
- ENCRYPTION_KEY_FILE is the location of the encrypted file that is
assets/server.key.inc
- JWT_KEY_FILE – the location to place the decrypted key file and the value should be
asset/server.key
- HUB_CONSUMER_KEY – the salesforce connected application id
- HUB_USER_NAME – the salesforce username that needs to perform the validation/deployment. ( This should be the deployment username )
- HUB_LOGIN_URL – the salesforce login URL depending upon whether it is the salesforce sandbox or production
- If you have multiple environments, then please add the secrets across all your environments
You can have the naming as per your need. If you use a different name then make sure to refer to those names in your pipeline If you have multiple environments, then make sure that the variable names are the same across all environments
Access Environment Secrets in your Pipeline
Add Environment in the build
Because we have set up the environment along with the secrets, first we need to tell our pipeline which environment the salesforce validation should be performed. The first step to modifying our job is build
and add an environment keyword like below
Refer to secrets in the steps
- To access the secrets within the GitHub Action pipeline we need to first use $ followed by double opening flower brackets( {{ ) & double closing flower brackets( }} ). Example
${{ }}
. - Within the flower, brackets use
secrets
the keyword followed by the period.
statement followed by the name of the secrets. For Example –${{ secrets.DECRYPTION_KEY }}
- Replace all the hardcoding values with the secrets that you have just created.
- Below is the modified code for the
build
job
build:
runs-on: ubuntu-latest
environment: developer
steps:
# Checkout the Source code from the latest commit
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install NPM
run: |
npm install
# Install the SFDX CLI using npm command
- name: Install the SFDX CLI
run: |
npm install sfdx-cli --global
sfdx force --help
- name: Decrypt the server.key.enc file & store inside assets folder
run: |
openssl enc -nosalt -aes-256-cbc -d -in ${{ secrets.ENCRYPTION_KEY_FILE }} -out ${{ secrets.JWT_KEY_FILE }} -base64 -K ${{ secrets.DECRYPTION_KEY }} -iv ${{ secrets.DECRYPTION_IV }}
- name: Authenticate Salesforce ORG
run: |
sfdx force:auth:jwt:grant --clientid ${{ secrets.HUB_CONSUMER_KEY }} --jwtkeyfile ${{ secrets.JWT_KEY_FILE }} --username ${{ secrets.HUB_USER_NAME }} --setdefaultdevhubusername -a HubOrg --instanceurl ${{ secrets.HUB_LOGIN_URL }}
- name: Validate Source Code Against Salesforce ORG
run: |
sfdx force:source:deploy -p force-app -c -u HubOrg
Commit and publish the changes. You will see that no Action is running because no changes have been made to the code base.
Test Environment based validation/deployment
To test the deployment or validation under the environment in my case developer
make any changes in the code base and publish the changes. You will clearly see that it is deploying on the mentioned environment.
If all the values are correct then you see the successful job below
Work with Pull Request in Github Action
It is ok to run the pipeline every time when there is a change in codebase pushed to a remote branch however when it comes to the higher environments like QA, staging, integration, or production then the pipeline should only execute when there is a pull_request raised and closed successfully.
Modify the developer pipeline
To take the most out of the Pull Request concept using Pipeline we need to make the following changes in our existing pipeline.
- Add
branches
filter in the push event - Below is the code for your reference
on:
push:
branches:
- feature/*
paths:
- 'force-app/**'
Create a new pipeline
We have successfully created and tested the pipeline for the developer environment and branch. Now let’s create another pipeline that will execute when the pull request is raised to the master branch and is merged.
- Create a new pipeline inside
.github/workflow
folder. You can give it any name, I will useproduction.yml
- Copy and paste the same code as
github-actions.yml
- Change the environment to
production
underbuild
job. Note:- This will require to create of a new environment with a nameproduction
and secrets setup - Change the name to
Production Pipeline
- change the
run-name
to${{ github.actor }} is running pipeline on ${{ github.repository }}
- for
on
use below code
on:
pull_request:
types: [closed]
branches:
- master
- main
paths:
- 'force-app/**'
Where
- branches: This pipeline should only execute when there is a PR raised to
master
branch - types: Pipeline will execute only when the PR is closed. You can see all values from the Official Document
- paths: only execute the pipeline when there is a change in
force-app
the folder that is a codebase
Create a New Branch
Because we have set up a production pipeline, to test the pipeline do follow the below steps
- Create a branch out of
maste or main
branch and name it the developer - Make changes in the codebase in
developer
branch - Create a Pull request from
developer
branch tomaster
ormain
branch - Merge the PR
- Notice that the Pipeline on the Master branch has been executed
If everything looks ok then you will see the success build like below
Work with Delta Deployment
Delta deployment is very important these days to achieve selective deployment because in our current approach, we are deploying everything that is inside force-app
no matter if we have changes in a single apex class it will deploy all the apex classes.
Because we are using sfdx deployment, we will be using an SFDX Plugin to generate the data at the run time. The plugin sfdx-git-delta is very helpful. This plugin is available for free and does not require any licensing.
Delta deployment will be helpful when we are deploying to the higher environment using Pull Request.
Create .sgdignore file
To make the Delta deployment using sfdx plugin, it is important to create the .sgdignore file and add the below content. We are creating this file because if you change the .yml file in the repo then the plugin will consider this file as workflow
and a new entry will be added in package.xml
which will fail the deployment.
The file must be in the topmost directory at the same level of force-app
folder
# Github Actions
.github/
.github/workflow
Install the sfdx-git-delta plugin in Pipeline
To install the plugin, add the new step before decrypting the sever.key.inc
file after the SFDX Installation step
- name: Install the sfdx-git-delta plugin
run: |
echo 'y' | sfdx plugins:install sfdx-git-delta
Generate package.xml for changed components only
When we talk about the delta deployment that means we need to generate the package.xml
at run time and the package.xml will contain only the component that has been changed by the developer.
Add the below step after the authentication with Salesforce
- name: Generate the package.xml for delta files
run: |
mkdir delta
sfdx sgd:source:delta --to "HEAD" --from "HEAD~1" --output "./delta" --ignore-whitespace -d -i .sgdignore
echo "--- package.xml generated with added and modified metadata ---"
cat delta/package/package.xml
Deploy delta components to Salesforce
After you have generated the package.xml
with the changed components only. Add the step to deploy the delta components to the salesforce
- name: Deploy Delta components to Salesofrce
run: |
sfdx force:source:deploy -x delta/package/package.xml -c -l RunLocalTests -u HubOrg
Commit & publish the YML file.
Note- Delete the other deployment step
Test the delta deployment
Because we are done with the changes that we need in the pipeline .yml file. Let’s make some changes to the code base while we are on the developer
branch. Create a pull request and merge the changes.
Click on the build Job to see the outcome of your Job. You will see the outcome like below
The deployment is failing due to some code coverage. If everything is ok your pipeline will be a success
Final Code for Production Pipeline
name: Production Pipeline
run-name: ${{ github.actor }} is running pipeline on ${{ github.repository }}
on:
pull_request:
types: [closed]
branches:
- master
- main
paths:
- 'force-app/**'
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v3
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
build:
runs-on: ubuntu-latest
environment: production
steps:
# Checkout the Source code from the latest commit
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install NPM
run: |
npm install
# Install the SFDX CLI using npm command
- name: Install the SFDX CLI
run: |
npm install sfdx-cli --global
sfdx force --help
- name: Install the sfdx-git-delta plugin
run: |
echo 'y' | sfdx plugins:install sfdx-git-delta
- name: Decrypt the server.key.enc file & store inside assets folder
run: |
openssl enc -nosalt -aes-256-cbc -d -in ${{ secrets.ENCRYPTION_KEY_FILE }} -out ${{ secrets.JWT_KEY_FILE }} -base64 -K ${{ secrets.DECRYPTION_KEY }} -iv ${{ secrets.DECRYPTION_IV }}
- name: Authenticate Salesforce ORG
run: |
sfdx force:auth:jwt:grant --clientid ${{ secrets.HUB_CONSUMER_KEY }} --jwtkeyfile ${{ secrets.JWT_KEY_FILE }} --username ${{ secrets.HUB_USER_NAME }} --setdefaultdevhubusername -a HubOrg --instanceurl ${{ secrets.HUB_LOGIN_URL }}
- name: Generate the package.xml for delta files
run: |
mkdir delta
sfdx sgd:source:delta --to "HEAD" --from "HEAD~1" --output "./delta" --ignore-whitespace -d -i .sgdignore
echo "--- package.xml generated with added and modified metadata ---"
cat delta/package/package.xml
- name: Deploy Delta components to Salesofrce
run: |
sfdx force:source:deploy -x delta/package/package.xml -c -l RunLocalTests -u HubOrg
Integrate the Static Code Analysis Tool
It is very important that we keep our code clean that follow all the best practices to get rid of technical debt in your code, making sure the code is not vulnerable, and other security issues are being taken care of at the early stage of the development
Install the SFDX CLI Scanner
Because Salesforce has its own plugin to perform the static code analysis. We will be using SFDX CLI Scanner plugin to analyze the code vulnerable.
Add the step to install the scanner in your pipeline before the deployment step
- name: Install the SFDX CLI Scanner
run: |
echo 'y' | sfdx plugins:install @salesforce/sfdx-scanner
Run the Code Analysis tool in the repo
The above step will install the scanner and now, we need to run the Scanner to scan all our code and generate the report. Add a new Step to scan the code
- name: Run SFDX CLI Scanner
run: |
sfdx scanner:run -f html -t "force-app" -e "eslint,retire-js,pmd,cpd" -c "Design,Best Practices,Code Style,Performance,Security" --outfile ./reports/scan-reports.html
Upload the Scan report as Artifacts
It is very important to store the scan result as artifacts so that developers can download and refer to the reports to make the changes to the code that may cause the technical debt
- uses: actions/upload-artifact@v3
with:
name: cli-scan-report
path: reports/scan-reports.html
Work with dependent Jobs
Sometimes you have a requirement that one job needs to wait until the other job has been completed. For Example, the job build
need to wait Explore-GitHub-Actions
job to be executed then only build
the job will execute.
Use needs
tag in the dependent job and add all the controlling job commas separated within []
.
build:
runs-on: ubuntu-latest
needs: [Explore-GitHub-Actions]
environment: developer
Make changes in the codebase, commit and publish the changes to execute the job.
Thanks for reading. Feedbacks are welcome