Guide: End-to-End Salesforce DevSecOps Automation
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:
- Set up a Git repository for your Salesforce project.
- 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:
- 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:
- Navigate to your project’s root directory in the terminal.
- 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 theorigin/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.
- Create a file named
arcMap.json
in your repository with the content of yourarcMap
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:
- Create a file named
arcMap.json
in your repository with the content of yourarcMap
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:
- 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.
- 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. - 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.
- 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. - 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.
- 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.
- 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.
- 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.