- What are Github Actions
- How To Setup CI/CD Using GitHub Actions for Salesforce
- Prepare your Salesforce Environment for Github Action CI/CD
- Install SFDX CLI in the pipeline
- Authenticate to Salesforce in Pipeline
- Path Filtering in Github Action
- Add Environments in Github Actions
- Configures Secrets in Github Action Environments
- Access Environment Secrets in your Pipeline
- Work with Pull Request in Github Action
- Work with Delta Deployment
- Integrate the Static Code Analysis Tool
- Work with dependent Jobs
- Useful Links for String Replacement
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.
- Create a
.github
folder within the parent directory of your Git Repo - Create a
workflow
folder within the.github
folder - Create a
GitHub-actions-demo.yml
file within theworkflow
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
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, if 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 it 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
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
Execute the below command within the folder where your server.key
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 quickly get
openssl enc -aes-256-cbc -k GITHUBACTIONS -P -md sha1 -nosalt
Execute the below command within the folder where your server.key
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
Now that we are done with the first step, let's push this code to our GitHub and see the GitHub Action Running
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 build
, 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 the npm command
- name: Install the SFDX CLI
run: |
npm install @salesforce/cli --global
sf --help
If you are making the changes in 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 a remote branch.
Note: The indentation is significant 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 Salesforce CLI using the npm command
- name: Install the Salesforce CLI
run: |
npm install @salesforce/cli --global
sf --help
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.
- Remember we encrypted the
server.key
file at the initial steps and placed the outcome inside theassets
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 the
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
After we have decrypted the server.key
in the previous and have got the key file that we need to for authentication. Now, the time is to authenticate to Salesforce Username using JWT. Below is the command for authentication
sf org login jwt --client-id YOUR_CLIENT_ID --jwt-key-file assets/server.key --username SALESFORCE_USERNAME --set-default --alias HubOrg --instance-url SALESFORCE_ORG_URL
Replace YOUR_CLIENT_ID with your salesforce-connected app consumer key Replace SALESFORCE_USERNAME with the salesforce deployment username Replace SALESFORCE_ORG_URL with the Salesforce Login Url
After making the changes, commit & push those changes to the remote branch and see the outcome! You must see the success message like below
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 sf command
sf project deploy validate --source-dir force-ap --test-level RunLocalTests --target-org HubOrg
OR
sf project deploy validate --manifest delta/package/package.xml --test-level RunLocalTests --target-org HubOrg
--source-dir
path to source code--target-org
the target org username that is HubOrg as we have used HubOrg as an alias in the authentication command
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 sf command
sf project deploy start --source-dir force-ap --test-level RunLocalTests --target-org HubOrg
OR
sf project deploy start --manifest delta/package/package.xml --test-level RunLocalTests --target-org HubOrg
--source-dir
path to source code--target-org
the target org username that is HubOrg as we have used HubOrg as an alias in the authentication 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 the npm command
- name: Install the SFDX CLI
run: |
npm install @salesforce/cli --global
sf --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: |
sf org login jwt --client-id HUB_CONSUMER_KEY --jwt-key-file JWT_KEY_FILE --username HUB_USER_NAME --set-default --alias HubOrg --instance-url HUB_LOGIN_URL
- name: Validate Source Code Against Salesforce ORG
run: |
sf project deploy start --source-dir force-app --test-level RunLocalTests --target-org HubOrg --coverage-formatters clover
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 no change in the code base. For example, if you change in the yml
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
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.
- Open the Repo where you wanted to create an environment
- Click on Setting tab to open the settings
- Locate Environment item from the left side
- Click on New Environment to create a New Environment
- Give a name & click on Configure Environment
Congratulation π you have created the environment. If you wanted to create more environment then follow the same steps
Because we are using the values directly in the yml
there are chances that some intruders 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 under theEnvironment 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 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 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
Because we have set up the environment alsong with the secrets, first we need to tell our pipeline which environment the salesforce validation should be performed. The first step to modify our job is build
and add an environment keyword like below
- 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 the
secrets
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 the npm command
- name: Install the SFDX CLI
run: |
npm install @salesforce/cli --global
sf --help
- name: Decrypt the server.key.enc file & store it inside the 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: |
sf org login jwt --client-id ${{ secrets.HUB_CONSUMER_KEY }} --jwt-key-file ${{ secrets.JWT_KEY_FILE }} --username ${{ secrets.HUB_USER_NAME }} --set-default --alias HubOrg --instance-url ${{ secrets.HUB_LOGIN_URL }}
- name: Validate Source Code Against Salesforce ORG
run: |
sf project deploy deploy --source-dir force-app --test-level RunLocalTests --target-org HubOrg --coverage-formatters clover
Commit and publish the changes. You will see that no Action is running because no changes have been made to the code base.
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 see that it is deploying on the mentioned environment.
If all the values are correct then you see the successful job like below
It is ok to run the pipeline whenever there is a change in codebase pushed to a remote branch however when it comes to a higher environment like QA, staging, integration, or production then the pipeline should only execute when there is a pull_request raised, and closed successfully.
To take the most out of the Pull Request concept using Pipeline we need to make the following changes in our existing pipeline.
- Add a
branches
filter in the push event - Below is the code for your reference
on:
push:
branches:
- feature/*
paths:
- 'force-app/**'
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 the
.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
under thebuild
job. Note:- This will require to create of a new environment with the 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 the below code
on:
pull_request:
types: [closed]
branches:
- master
- main
paths:
- 'force-app/**'
- branches: This pipeline should only execute when there is a PR raised to the
master
branch - types: Pipeline will execute only when the PR is closed. You can see all values from Official Document
- paths: only execute the pipeline when there is a change in the
force-app
folder that is a codebase
Because we have set up a production pipeline, to test the pipeline do follow the below steps
- Create a branch out of the
master or main
branch and name it the developer - Make changes in the codebase in the
developer
branch - Create a Pull Request from the
developer
branch to themaster
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
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 a 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 licencing.
Delta deployment will be helpful when we are deploying to the higher environment using Pull Request.
To make the Delta deployment using the 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 as
force-app
folder
# Github Actions
.github/
.github/workflow
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' | sf plugins install sfdx-git-delta
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
sf 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
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 Salesforce
run: |
sf project deploy start --manifest delta/package/package.xml --test-level RunLocalTests --target-org HubOrg --coverage-formatters clover
Commit & publish the yml file.
Note- Delete the other deployment step
Because we are done with the changes 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 below
The deployment is failing due to some code coverage. If everything is ok your pipeline will be a success
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 the npm command
- name: Install the SFDX CLI
run: |
npm install @salesforce/cli --global
sf --help
- name: Install the sfdx-git-delta plugin
run: |
echo 'y' | sf plugins install sfdx-git-delta
- name: Decrypt the server.key.enc file & store it 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: |
sf org login jwt --client-id ${{ secrets.HUB_CONSUMER_KEY }} --jwt-key-file ${{ secrets.JWT_KEY_FILE }} --username ${{ secrets.HUB_USER_NAME }} --set-default --alias HubOrg --instance-url ${{ secrets.HUB_LOGIN_URL }}
- name: Generate the package.xml for delta files
run: |
mkdir delta
sf 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 Salesforce
run: |
sf project deploy start --manifest delta/package/package.xml --test-level RunLocalTests --target-org HubOrg --coverage-formatters clover
we must keep our code clean that follows 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
Because Salesforce has its plugin to perform the static code analysis. We will use the SFDX CLI Scanner plugin to analyze the vulnerable code.
Add the step to install the scanner in your pipeline before deployment step
- name: Install the SFDX CLI Scanner
run: |
echo 'y' | sf plugins install @salesforce/sfdx-scanner
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: |
sf 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
It is very important to store the scan result as artifacts so that developers can download and refer the reports to make the changes into the code that may cause the technical debt
- uses: actions/upload-artifact@v3
with:
name: cli-scan-report
path: reports/scan-reports.html
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 for the Explore-GitHub-Actions
job to be executed then only the build
job will execute.
Use the 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.
https://githubflow.github.io/ https://nvie.com/posts/a-successful-git-branching-model/ https://stackoverflow.com/questions/62325286/run-github-actions-when-pull-requests-have-a-specific-label https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_review