Newer Version Available

This content describes an older version of this product. View Latest

Jenkinsfile Walkthrough

The sample Jenkinsfile shows how to integrate Salesforce DX into a Jenkins job. The sample uses Jenkins multibranch pipelines. Every Jenkins setup is different. This walkthrough describes one of the ways to automate testing of your Salesforce applications. The walkthrough highlights the Salesforce DX CLI commands to create a scratch org, upload your code, and run your tests.

We assume that you are familiar with the structure of the Jenkinsfile, Jenkins Pipeline DSL, and the Groovy programming language. This walkthrough focuses solely on Salesforce DX information. See the Salesforce DX Command Reference regarding the commands used.

This Salesforce DX workflow most closely corresponds to Jenkinsfile stages.

Define Variables

Use the def keyword to define the variables required by the Salesforce DX CLI commands. Assign each variable the corresponding environment variable that you previously set in your Jenkins environment.

1def HUB_ORG=env.HUB_ORG_DH
2def SFDC_HOST = env.SFDC_HOST_DH
3def JWT_KEY_CRED_ID = env.JWT_CRED_ID_DH
4def CONNECTED_APP_CONSUMER_KEY=env.CONNECTED_APP_CONSUMER_KEY_DH

Define the SFDC_USERNAME variable, but don’t set its value. You do that later.

1def SFDC_USERNAME

Although not required, we assume you’ve used the Jenkins Global Tool Configuration to create the toolbelt custom tool that points to the CLI installation directory. In your Jenkinsfile, use the tool command to set the value of the toolbelt variable to this custom tool.

1def toolbelt = tool 'toolbelt'

You can now reference the Salesforce CLI executable in the Jenkinsfile using ${toolbelt}/sfdx.

Check Out the Source Code

Before testing your code, get the appropriate version or branch from your version control system (VCS) repository. In this example, we use the checkout scm Jenkins command. We assume that the Jenkins administrator has already configured the environment to access the correct VCS repository and check out the correct branch.

1stage('checkout source') {
2        // when running in multi-branch job, one must issue this command
3        checkout scm
4  }

Wrap All Stages in a withCredentials Command

You previously stored the JWT private key file as a Jenkins Secret File using the Credentials interface. Therefore, you must use the withCredentials command in the body of the Jenkinsfile to access the secret file. The withCredentials command lets you name a credential entry, which is then extracted from the credential store and provided to the enclosed code through a variable. When using withCredentials, put all stages within its code block.

This example stores the credential ID for the JWT key file in the variable JWT_KEY_CRED_ID. You defined JWT_KEY_CRED_ID earlier and set it to its corresponding environment variable. The withCredentials command fetches the contents of the secret file from the credential store and places the contents in a temporary location. The location is stored in the variable jwt_key_file. You use the jwt_key_file variable with the force:auth:jwt command to specify the private key securely.

1withCredentials([file(credentialsId: JWT_KEY_CRED_ID, variable: 'jwt_key_file')]) {
2   # all stages will go here 
3}

Authorize Your Dev Hub Org and Create a Scratch Org

The dreamhouse-sfdx example uses one stage to authorize the Dev Hub org and create a scratch org.

1stage('Create Scratch Org') {
2
3   rc = sh returnStatus: true, script: "${toolbelt}/sfdx force:auth:jwt:grant --clientid ${CONNECTED_APP_CONSUMER_KEY} --username ${HUB_ORG} --jwtkeyfile ${jwt_key_file} --setdefaultdevhubusername --instanceurl ${SFDC_HOST}"
4   if (rc != 0) { error 'hub org authorization failed' }
5
6   // need to pull out assigned username
7   rmsg = sh returnStdout: true, script: "${toolbelt}/sfdx force:org:create --definitionfile config/project-scratch-def.json --json --setdefaultusername"
8   printf rmsg
9   def jsonSlurper = new JsonSlurperClassic()
10   def robj = jsonSlurper.parseText(rmsg)
11   if (robj.status != "ok") { error 'org creation failed: ' + robj.message }
12   SFDC_USERNAME=robj.username
13   robj = null
14 
15}

Use the force:auth:jwt:grant CLI command to authorize your Dev Hub org.

You are required to run this step only once, but we suggest you add it to your Jenkinsfile and authorize each time you run the Jenkins job. This way you’re always sure that the Jenkins job is not aborted due to lack of authorization. There is typically little harm in authorizing multiple times, although keep in mind that the API call limit for your scratch org’s edition still applies.

Use the parameters of the force:auth:jwt:grant command to provide information about the Dev Hub org that you are authorizing. The values for the --clientid, --username, and --instanceurl parameters are the CONNECTED_APP_CONSUMER_KEY, HUB_ORG, and SFDC_HOST environment variables you previously defined, respectively. The value of the --jwtkeyfile parameter is the jwt_key_file variable that you set in the previous section using the withCredentials command. The --setdefaultdevhubusername parameter specifies that this HUB_ORG is the default Dev Hub org for creating scratch orgs.

Use the force:org:create CLI command to create a scratch org. In the example, the CLI command uses the config/project-scratch-def.json file (relative to the project directory) to create the scratch org. The --json parameter specifies that the output be in JSON format. The --setdefaultusername parameter sets the new scratch org as the default.

The Groovy code that parses the JSON output of the force:org:create command extracts the username that was auto-generated as part of the org creation. This username, stored in the SFDC_USERNAME variable, is used with the CLI commands that push source, assign a permission set, and so on.

Push Source and Assign a Permission Set

Let’s populate your new scratch org with metadata. This example uses the force:source:push command to upload your source to the org. The source includes all the pieces that make up your Salesforce application: Apex classes and test classes, permission sets, layouts, triggers, custom objects, and so on.

1stage('Push To Test Org') {
2
3    rc = sh returnStatus: true, script: "${toolbelt}/sfdx force:source:push --targetusername ${SFDC_USERNAME}"
4   if (rc != 0) {
5 	error 'push all failed'
6   }
7   // assign permset
8   rc = sh returnStatus: true, script: "${toolbelt}/sfdx force:user:permset:assign --targetusername ${SFDC_USERNAME} --permsetname DreamHouse"
9   if (rc != 0) {
10	error 'push all failed'
11   }
12}

Recall the SFDC_USERNAME variable that contains the auto-generated username that was output by the force:org:create command in an earlier stage. The code uses this variable as the argument to the --targetusername parameter to specify the username for the new scratch org.

The force:source:push command pushes all the Salesforce-related files that it finds in your project. Add a .forceignore file to your repository to list the files that you do not want pushed to the org.

After pushing the metadata, the example uses the force:user:permset:assign command to assign a permission set (named DreamHouse) to the SFDC_USERNAME user. The XML file that describes this permission set was uploaded to the org as part of the push.

Run Apex Tests

Now that your source code and test source have been pushed to the scratch org, run the force:apex:test:run command to run Apex tests.

1stage('Run Apex Test') {
2   sh "mkdir -p ${RUN_ARTIFACT_DIR}"
3   timeout(time: 120, unit: 'SECONDS') {
4   	rc = sh returnStatus: true, script: "${toolbelt}/sfdx force:apex:test:run --testlevel RunLocalTests --outputdir ${RUN_ARTIFACT_DIR} --resultformat tap --targetusername ${SFDC_USERNAME}"
5	if (rc != 0) {
6		error 'apex test run failed'
7	}
8   }
9}

You can specify various parameters to the force:apex:test:run CLI command. In the example:

  • The --testlevel RunLocalTests option runs all tests in the scratch org, except tests that originate from installed managed packages. You can also specify RunSpecifiedTests to run only certain Apex tests or suites or RunAllTestsInOrg to run all tests in the org.
  • The --outputdir option uses the RUN_ARTIFACT_DIR variable to specify the directory into which the test results are written. Test results are produced in JUnit and JSON formats.
  • The --resultformat tap option specifies that the command output is in Test Anything Protocol (TAP) format. The test results that are written to a file are still in JUnit and JSON formats.
  • The --targetusername option specifies the username for accessing the scratch org (the value in SFDC_USERNAME).

The force:apex:test:run command writes its test results in JUnit format. You can collect the results using industry-standard tools as shown in the following example.

1stage('collect results') {
2        junit keepLongStdio: true, testResults: 'tests/**/*-junit.xml'
3    }

Delete the Scratch Org

Salesforce reserves the right to delete a scratch org a specified number of days after it was created. You can also create a stage in your pipeline that uses force:org:delete to explicitly delete your scratch org when the tests complete. This cleanup ensures better management of your resources.

1stage('Delete Test Org') {
2
3        timeout(time: 120, unit: 'SECONDS') {
4            rc = sh returnStatus: true, script: "${toolbelt}/sfdx force:org:delete --targetusername ${SFDC_USERNAME} --noprompt"
5            if (rc != 0) {
6                error 'org deletion request failed'
7            }
8        }
9    }