Package Your App and Automate CI/CD
Now that you have the app source code hosted on GitLab.com and you’ve added CI/CD environment variables, it’s time to automate.
In this step, you create an unlocked package of your DreamHouse project and install it into your Trailhead Playground.
- From the command line, navigate to your DreamHouse-sfdx directory, if you’re not there already.
- Create an unlocked package named
sfdx force:package:create --path force-app --name "DreamHouse" --description "GitLab CI Package Example" --packagetype Unlocked
You should see something like the following:
sfdx-project.json has been updated. Successfully created a package. 0Ho1U000000fxczSAA === Ids NAME VALUE ────────── ────────────────── Package Id 0Ho1U000000fxd4SAA
You have successfully created an unlocked package of your DreamHouse project. The package name (also referred to as the package alias) is now associated with the Package Id (0Ho). Salesforce CLI commands that accept Package Ids as arguments also accept these user-friendly aliases. How convenient!
The package alias to package id mappings were automatically added to your sfdx-project.json file of the project when the package was created. Now you need to commit these changes to GitLab.
- Add the
sfdx-project.jsonfile to a git commit with a message describing this change.
git add sfdx-project.json git commit -m "Add DreamHouse package id and alias"
- Push the changes to GitLab.
git push -u origin master
So far you've created the definition of the DreamHouse unlocked package. The package starts empty with no metadata in it just like a new change set starts empty. In the next steps, you automate the creation of new package versions. A package version represents a snapshot of your project's metadata that you then can install into orgs, such as scratch orgs, sandboxes, and production.
.gitlab-ci.yml file in your repository. This will define stages of testing, integration, and deployment to automate delivery of your Salesforce application. The pipeline automatically uses scratch orgs for these steps (for example,
unit testing) and deploys the final version of your application post-approval to a persistent environment, such as your Trailhead Playground.
This is essentially your blueprint for automation.
- Log in to https://gitlab.com if you’re not already there.
- Click Projects dropdown and search for
DreamHouseto find your cloned repository, and select DreamHouse-sfdx.
- Click Repository then Files.
- Click Web IDE.
- Click the New File icon
just above the directory.
- Click .gitlab-ci.yml to automatically create a new file with the same name.
- In the .gitlab-ci.yml text editor that opens, enter the following contents. We added comments indicated by the hashtag (#) so you can inspect what’s happening. You can also check out
GitLab Docs if you want to learn more about yml files.
# # GitLab CI/CD Pipeline for deploying DreamHouse App using Salesforce DX # # # Run these commands before executing any build jobs, # such as to install dependencies and set environment variables # before_script: # Decrypt server key - openssl enc -aes-256-cbc -md sha256 -salt -d -in assets/server.key.enc -out assets/server.key -k $SERVER_KEY_PASSWORD -pbkdf2 # Install jq, a json parsing library - apt update && apt -y install jq # Setup SFDX environment variables # https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_cli_env_variables.htm - export SALESFORCE_CLI_URL=https://developer.salesforce.com/media/salesforce-cli/sfdx-linux-amd64.tar.xz - export SFDX_AUTOUPDATE_DISABLE=false - export SFDX_USE_GENERIC_UNIX_KEYCHAIN=true - export SFDX_DOMAIN_RETRY=600 - export SFDX_LOG_LEVEL=DEBUG # Install Salesforce CLI - mkdir sfdx - wget -qO- $SALESFORCE_CLI_URL | tar xJ -C sfdx --strip-components 1 - './sfdx/install' - export PATH=./sfdx/$(pwd):$PATH # Output CLI version and plug-in information - sfdx update - sfdx --version - sfdx plugins --core # # Define the stages of our pipeline # stages: - code-testing - integration-testing - app-deploy # # Stage 1 -- Create a scratch org for code testing # code-testing: stage: code-testing script: # Authenticate to the Dev Hub using the server key - sfdx force:auth:jwt:grant --setdefaultdevhubusername --clientid $SF_CONSUMER_KEY --jwtkeyfile assets/server.key --username $SF_USERNAME # Create scratch org - sfdx force:org:create --setdefaultusername --definitionfile config/project-scratch-def.json --wait 10 --durationdays 7 - sfdx force:org:display # Push source to scratch org (this is with source code, all files, etc) - sfdx force:source:push # Assign DreamHouse permission set to scratch org default user - sfdx force:user:permset:assign --permsetname DreamHouse # Add sample data into app - sfdx force:data:tree:import --plan data/sample-data-plan.json # Unit Testing - sfdx force:apex:test:run --wait 10 --resultformat human --codecoverage --testlevel RunLocalTests # Delete Scratch Org - sfdx force:org:delete --noprompt # # Stage 2 -- Create a scratch org, create a package version, and push into org for testing # integration-testing: # Specify file paths that we want available # in downstream build stages for this pipeline execution. # This is a way to pass dynamic values from one stage to another. artifacts: paths: - PACKAGE_VERSION_ID.TXT - SCRATCH_ORG_USERNAME.TXT stage: integration-testing script: # Authenticate to the Dev Hub using the server key - sfdx force:auth:jwt:grant --setdefaultdevhubusername --clientid $SF_CONSUMER_KEY --jwtkeyfile assets/server.key --username $SF_USERNAME # Create scratch org - sfdx force:org:create --setdefaultusername --definitionfile config/project-scratch-def.json --wait 10 --durationdays 7 - sfdx force:org:display # Increment package version number - echo $PACKAGE_NAME - PACKAGE_VERSION_JSON="$(eval sfdx force:package:version:list --concise --released --packages $PACKAGE_NAME --json | jq '.result | sort_by(-.MajorVersion, -.MinorVersion, -.PatchVersion, -.BuildNumber) | . // ""')" - echo $PACKAGE_VERSION_JSON - IS_RELEASED=$(jq -r '.IsReleased?' <<< $PACKAGE_VERSION_JSON) - MAJOR_VERSION=$(jq -r '.MajorVersion?' <<< $PACKAGE_VERSION_JSON) - MINOR_VERSION=$(jq -r '.MinorVersion?' <<< $PACKAGE_VERSION_JSON) - PATCH_VERSION=$(jq -r '.PatchVersion?' <<< $PACKAGE_VERSION_JSON) - BUILD_VERSION="NEXT" - if [ -z $MAJOR_VERSION ]; then MAJOR_VERSION=1; fi; - if [ -z $MINOR_VERSION ]; then MINOR_VERSION=0; fi; - if [ -z $PATCH_VERSION ]; then PATCH_VERSION=0; fi; - if [ "$IS_RELEASED" == "true" ]; then MINOR_VERSION=$(($MINOR_VERSION+1)); fi; - VERSION_NUMBER="$MAJOR_VERSION.$MINOR_VERSION.$PATCH_VERSION.$BUILD_VERSION" - echo $VERSION_NUMBER # Create packaged version - export PACKAGE_VERSION_ID="$(eval sfdx force:package:version:create --package $PACKAGE_NAME --versionnumber $VERSION_NUMBER --installationkeybypass --wait 10 --json | jq -r '.result.SubscriberPackageVersionId')" # Save your PACKAGE_VERSION_ID to a file for later use during deploy so you know what version to deploy - echo "$PACKAGE_VERSION_ID" > PACKAGE_VERSION_ID.TXT - echo $PACKAGE_VERSION_ID # Install package in DevHub org (this is a compiled library of the app) - sfdx force:package:list - sfdx force:package:install --package $PACKAGE_VERSION_ID --wait 10 --publishwait 10 --noprompt # Assign DreamHouse permission set to scratch org default user - sfdx force:user:permset:assign --permsetname DreamHouse # Add sample data into app - sfdx force:data:tree:import --plan data/sample-data-plan.json # Run unit tests in scratch org - sfdx force:apex:test:run --wait 10 --resultformat human --codecoverage --testlevel RunLocalTests # Get the username for the scratch org - export SCRATCH_ORG_USERNAME="$(eval sfdx force:user:display --json | jq -r '.result.username')" - echo "$SCRATCH_ORG_USERNAME" > ./SCRATCH_ORG_USERNAME.TXT # Generate a new password for the scrach org - sfdx force:user:password:generate - echo -e "\n\n\n\n" # Display username, password, and instance URL for login # Be careful not to do this in a publicly accessible pipeline as it exposes the credentials of your scratch org - sfdx force:user:display # # Stage 3 -- Promote the package to downstream environment for UAT for example # app-deploy: stage: app-deploy # This stage must be started manually as an example of # conditional stages that need to wait for an approval, # such as waiting for QA signoff from the previous stage. when: manual script: # Read the scratch org username from file created in prior stage - export SCRATCH_ORG_USERNAME=`cat ./SCRATCH_ORG_USERNAME.TXT` - echo $SCRATCH_ORG_USERNAME # Authenticate with your playground or sandbox environment - sfdx force:auth:jwt:grant --setdefaultdevhubusername --clientid $SF_CONSUMER_KEY --jwtkeyfile assets/server.key --username $SF_USERNAME - sfdx force:config:set defaultusername=$SF_USERNAME # Delete Scratch Org that you were inspecting from your browser - sfdx force:data:record:delete --sobjecttype ScratchOrgInfo --where "SignupUsername='$SCRATCH_ORG_USERNAME'" # Read the package version id from file created in prior stage - export PACKAGE_VERSION_ID=`cat ./PACKAGE_VERSION_ID.TXT` - echo $PACKAGE_VERSION_ID # Promote the package version - sfdx force:package:version:promote --package $PACKAGE_VERSION_ID --noprompt # Install the package version - sfdx force:package:install --package $PACKAGE_VERSION_ID --wait 10 --publishwait 10 --noprompt
- Click Commit... to stage the change in Web IDE.
- Click Commit... again to open the commit form in Web IDE.
- If the master branch is not selected, select the master branch and then click Stage & Commit to save the change to your GitLab repository. And now that your repository has a GitLab Pipelines configuration file, GitLab will automatically
start your first CI/CD pipeline run.
You just set up some powerful automation that involves the creation of multiple scratch orgs, the deployment and testing of your Apex code, and creating and installing new unlocked package versions into your orgs.
In this yml file, we specify that scratch orgs will be automatically deleted after 7 days. In practice, we recommend setting the duration to 1 day. To change the duration, update the
--durationdays parameter when you run
sfdx force:org:create to the desired length of time.
Let’s see the CI/CD pipeline you just created.
- Click Projects dropdown and search for
DreamHouseto find your cloned repository, and select DreamHouse-sfdx.
- Click CI / CD then Pipelines.
- You should see your pipeline running, indicated by “running” under the Status column. Click the pipeline's status to see more.
- Drill down even further by clicking the code-testing or integration-testing jobs to see their console outputs.
Let the pipeline run, it can take several minutes. It will stop after completing the
integration-testing stage because the third stage,
app-deploy, is configured to start manually rather than automatically.
Why require app deployment to be manual?
That's a great question! GitLab Pipelines are a great tool for automating continuous integration and deployment. In this project, the pipeline you set up assumes there is an approval process before installing the new package version. You definitely want to have a review before updating your app.
If your pipeline is still running, maybe have another drink of water, and appreciate the fact that you have a CI/CD pipeline running, you savvy developer, you.
While you’re waiting, let’s talk about what’s happening on the Salesforce platform right now. With the GitLab pipeline running, Salesforce scratch orgs are being created to execute your Apex tests. Then they’re deleted once the tests are complete. It’s almost like the org never existed, as if a passing dream, one of the reasons scratch orgs are also known as ephemeral orgs.
In this case, two scratch orgs are created and then taken down—one for the
code-testing stage, which validates that the unpackaged metadata is deployable and passes your tests, and one for the
integration-testing stage, which
validates that the metadata is packageable.