Guide: End-to-End Salesforce DevSecOps Automation

Nomadic Programmer
7 min readAug 17, 2023

Introduction:

In this guide, we’ll take you through the entire process of streamlining your Salesforce DevSecOps workflow, from sourcing code and managing profiles to automating deployments using various CI/CD pipelines. We’ll explore best practices, tools, and techniques to enhance your Salesforce development lifecycle.

Prerequisites:

  • Install Salesforce CLI (sfdx) installed
npm i sfdx-cli
  • Install sfdx-mdt plugin
npm install -g sfdx-mdt-plugin
  • Git repository with Salesforce metadata

Step 1: Source Salesforce Code and Folder Structure:

  1. Set up a Git repository for your Salesforce project.
  2. Organize your Salesforce metadata in a coherent folder structure:
├── force-app
│ ├── main
│ │ ├── default
│ │ └── custom
│ ├── profiles
│ │ ├── Admin.profile-meta.xml
│ │ └── ...
│ └── ...

Step 2: Handle Profiles Using sfdx mdf Plugin:

  1. Install the sfdx mdf plugin:
sfdx plugins:install sfdx-metadata-field-comparator

2. Use the sfdx mdf:apply command to apply field-level security changes from a source profile to target profiles.

Step 3: Explain sfdx mdt:delta Command in Detail:

  1. Navigate to your project’s root directory in the terminal.
  2. Run the following command to retrieve metadata deltas, including deleted components:
sfdx mdt:delta -c origin/master -d deltas --deleted
  • -c origin/master: Compares the current state of your metadata with the origin/master branch in your Git repository.
  • -d deltas: Specifies the destination folder for the delta output.
  • --deleted: Includes information about deleted metadata components.

Step 4: CI/CD Pipeline Examples:

Jenkins Pipeline:

  • Set up a Jenkins job with the following stages:
  • Checkout code from Git repository.
  • Authenticate with Salesforce org using sfdx CLI.
  • Run tests using sfdx force:apex:test:run command.
  • Get metadata deltas using sfdx mdt:delta command.
  • Deploy metadata changes using sfdx force:source:deploy command.
  • Refresh lower sandboxes using sfdx force:org:refresh command.

GitHub Actions Workflow:

  • Create a .github/workflows directory in your repository.
  • Set up a GitHub Actions workflow YAML file with steps similar to the Jenkins pipeline.

Harness IO Pipeline:

  • Sign up for a Harness account if you haven’t already.
  • Create an Application in Harness for your Salesforce project.
  • Configure a workflow with steps for authentication, testing, getting deltas, deploying changes, and sandbox management.
  • Integrate Harness with your Git repository.

Jenkins Pipeline Example:

def arcMap = [
'Feature/*': [
'sfdx-profile': 'dev',
'destination-branch': 'origin/Development',
'runTest': 'False'
],
'Development': [
'sfdx-profile': 'test',
'destination-branch': 'origin/Integration',
'runTest': 'False'
],
'Integration': [
'sfdx-profile': '',
'destination-branch': 'origin/master',
'runTest': 'False'
],
'tag/*': [
'sfdx-profile': '',
'destination-branch': 'tag/*',
'runTest': 'true'
]
]

pipeline {
agent any

stages {
stage('Checkout') {
steps {
// Checkout code from Git repository
git 'https://github.com/your-username/your-salesforce-repo.git'
}
}

stage('Authenticate') {
steps {
script {
def branch = env.BRANCH_NAME
def branchConfig = arcMap[branch]

if (branchConfig) {
// Authenticate with Salesforce org using sfdx CLI with branch-specific configuration
sh "sfdx force:auth:web:login -a ${branchConfig['sfdx-profile']}"
}
}
}
}

// ... (other stages)
}

post {
always {
script {
def branch = env.BRANCH_NAME
def branchConfig = arcMap[branch]

if (branchConfig) {
// Clean up authentication tokens with branch-specific configuration
sh "sfdx force:auth:logout -a ${branchConfig['sfdx-profile']}"
}
}
}
}
}
def arcMap = [
'Feature/*': [
'sfdx-profile': 'dev',
'destination-branch': 'origin/Development',
'runTest': 'False'
],
'Development': [
'sfdx-profile': 'test',
'destination-branch': 'origin/Integration',
'runTest': 'False'
],
'Integration': [
'sfdx-profile': '',
'destination-branch': 'origin/master',
'runTest': 'False'
],
'tag/*': [
'sfdx-profile': '',
'destination-branch': 'tag/*',
'runTest': 'true'
]
]

Let’s go through each part of the arcMap dictionary:

  • 'Feature/*', 'Development', 'Integration', 'tag/*': These are branch patterns. They define which branches in your Git repository should be matched to specific configurations. The asterisk * acts as a wildcard to match any text.
  • 'sfdx-profile': This property specifies the Salesforce profile to use for authenticating with Salesforce org during the build process. It indicates which set of permissions and access levels are applied.
  • 'destination-branch': This property specifies the target branch or tag for deployments or comparisons. For example, if you're building the 'Feature/*' branch, the metadata changes may be deployed to the 'Development' branch.
  • 'runTest': This property specifies whether tests should be executed during the deployment process. 'False' indicates that tests should not be run, while 'true' indicates that tests should be run.

With this branch mapping, you can dynamically configure various aspects of your DevSecOps pipeline based on the branch being built. For example, when building the 'Feature/*' branch, you might use the 'dev' profile for authentication, deploy to the 'Development' branch, and skip running tests. Conversely, when building the 'tag/*' branches, you might run tests and deploy to branches that match the tag pattern.

In the Jenkins pipeline example, the script uses the arcMap dictionary to dynamically select the appropriate configuration based on the branch being built. This allows you to handle different branches with distinct Salesforce profiles, deployment targets, and test execution settings.

GitHub Actions Workflow Example:

Please note that this YAML structure doesn’t support dynamic branch matching as the scripting languages did. You would need to manually reference the keys for each specific branch.

  1. Create a file named arcMap.json in your repository with the content of your arcMap configuration:
{
"Feature/*": {
"sfdx-profile": "dev",
"destination-branch": "origin/Development",
"runTest": "False"
},
"Development": {
"sfdx-profile": "test",
"destination-branch": "origin/Integration",
"runTest": "False"
},
"Integration": {
"sfdx-profile": "",
"destination-branch": "origin/master",
"runTest": "False"
},
"tag/*": {
"sfdx-profile": "",
"destination-branch": "tag/*",
"runTest": "true"
}
}

2. Modify your GitHub Actions workflow YAML to read the JSON file and use its contents for branch-specific configurations:

name: Salesforce Automation

on:
push:
branches:
- '*'

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Read arcMap JSON
id: read-arcmap
run: |
echo "::set-output name=arcmap::$(cat arcMap.json | jq -r tostring)"
shell: bash

- name: Load arcMap JSON
id: load-arcmap
run: |
echo "${{ steps.read-arcmap.outputs.arcmap }}" > arcMap.json

- name: Authenticate with Salesforce org
run: |
branch=$GITHUB_REF
branchConfig=$(jq -r ".[\"$branch\"]" arcMap.json)
sfdx-profile=$(echo "$branchConfig" | jq -r '.["sfdx-profile"]')
if [ -n "$sfdx-profile" ]; then
sfdx force:auth:web:login -a $sfdx-profile
fi

- name: Run Tests
run: sfdx force:apex:test:run

- name: Install sfdx:mdt plugin
run: sfdx plugins:install sfdx-mdd-toolkit

- name: Get Metadata Deltas
run: |
branch=$GITHUB_REF
branchConfig=$(jq -r ".[\"$branch\"]" arcMap.json)
if [ -n "$branchConfig" ]; then
sfdx mdt:delta -c origin/master -d deltas --deleted
fi

- name: Deploy Metadata
run: |
branch=$GITHUB_REF
branchConfig=$(jq -r ".[\"$branch\"]" arcMap.json)
if [ -n "$branchConfig" ]; then
sfdx force:source:deploy -u $sfdx-profile -p force-app
fi

- name: Refresh Sandboxes
run: |
branch=$GITHUB_REF
branchConfig=$(jq -r ".[\"$branch\"]" arcMap.json)
if [ -n "$branchConfig" ]; then
sfdx force:org:refresh -u lower-sandbox-username
fi

Harness IO Pipeline Example:

  1. Create a file named arcMap.json in your repository with the content of your arcMap configuration:
{
"Feature/*": {
"sfdx-profile": "dev",
"destination-branch": "origin/Development",
"runTest": "False"
},
"Development": {
"sfdx-profile": "test",
"destination-branch": "origin/Integration",
"runTest": "False"
},
"Integration": {
"sfdx-profile": "",
"destination-branch": "origin/master",
"runTest": "False"
},
"tag/*": {
"sfdx-profile": "",
"destination-branch": "tag/*",
"runTest": "true"
}
}

2. In your Harness configuration, create a pipeline and use the arcMap.json content to define environment variables for each branch configuration. Here's a simplified example YAML:

version: 1.0
type: PROVISION_INFRA
steps:
- name: Read arcMap JSON
type: SHELL_SCRIPT
command: echo ${{fileContents("arcMap.json")}}
timeoutMillis: 600000
continueOnError: false
envVariables:
arcMapJson: ${{fileContents("arcMap.json")}}
- name: Load arcMap JSON into env variables
type: SHELL_SCRIPT
command: |
arcMap=$arcMapJson
echo "ARC_MAP=$arcMap" >> $METAENV
timeoutMillis: 600000
continueOnError: false
- name: Authenticate with Salesforce org
type: SHELL_SCRIPT
command: |
arcMapJson=$ARC_MAP
branch=$(git symbolic-ref --short HEAD)
sfdxProfile=$(echo $arcMapJson | jq -r ".[\"$branch\"].sfdx-profile")
if [ -n "$sfdxProfile" ]; then
sfdx force:auth:web:login -a $sfdxProfile
fi
timeoutMillis: 600000
continueOnError: false
# Add more steps for other actions in your pipeline

Conclusion: Mastering Salesforce DevSecOps Automation

In this guide, we embarked on an exciting journey to streamline and elevate your Salesforce development process through DevSecOps automation. By integrating robust tools and best practices, you’ve empowered your team to collaborate seamlessly, enhance code quality, and accelerate innovation.

From sourcing Salesforce code to orchestrating complex CI/CD pipelines, you’ve harnessed the power of modern automation to transform the way you develop, test, and deploy on the Salesforce platform.

Key Takeaways:

  1. Sourcing Code and Folder Structure: Establish a structured folder layout for your Salesforce metadata, promoting organization and clarity. The proper arrangement of your metadata is the foundation for efficient version control and automated deployment.
  2. Handling Profiles with sfdx-mdf Plugin: The sfdx-mdf plugin enables fine-grained control over field-level security changes in profiles. By managing profiles effectively, you ensure consistent security settings across environments.
  3. Dynamic Sandbox and Branch Strategy: Craft a versatile sandbox and branch strategy that aligns with your development needs. By utilizing distinct sandboxes for development, integration, testing, and staging, you facilitate a smooth progression of changes.
  4. Metadata Deltas with sfdx-mdt Plugin: Leverage the sfdx-mdt plugin to efficiently capture and compare metadata deltas, including deletions. This technique ensures that your deployments only include necessary changes, minimizing risk and speeding up deployment times.
  5. End-to-End Automation with CI/CD Pipelines: Embrace the power of Continuous Integration and Continuous Deployment (CI/CD) pipelines to automate your Salesforce development lifecycle. Jenkins, GitHub Actions, and Harness IO provide robust platforms for orchestrating and automating complex workflows.
  6. Environment-Specific Authentication: Automate authentication to Salesforce orgs based on the branch being built. This ensures that your automation processes are aligned with the specific requirements of each development phase.
  7. Customized Deployment: Deploy metadata changes according to the branch configuration, taking advantage of the flexibility to include or skip tests based on the context. This level of customization guarantees smooth and controlled deployments.
  8. Collaborative Sandbox Management: Employ automated sandbox refreshes to maintain consistent and up-to-date environments. This approach ensures that development, testing, and staging environments remain synchronized.

As you continue to refine and expand your Salesforce DevSecOps practices, remember that successful automation requires ongoing adaptation and optimization. Tailor your processes to your team’s unique needs, iteratively enhancing your pipeline for efficiency and effectiveness.

By embracing the principles of automation, collaboration, and continuous improvement, you’ve unlocked the potential to drive innovation, accelerate delivery, and elevate the quality of your Salesforce projects. Congratulations on embarking on this transformative journey, and may your Salesforce DevSecOps automation continue to flourish and evolve in the dynamic landscape of modern development.

--

--