CumulusCI’s Task Framework
CumulusCI is a powerful open source framework for automating delivery of Salesforce products and solutions. While CumulusCI comes with a rich library of tasks, you may find a need to implement custom logic.
CumulusCI’s task framework provides much of the scaffolding needed so writing a custom task is mostly focused on implementing the business logic in Python. Understanding CumulusCI’s task framework will help you avoid reinventing the wheel when creating custom tasks.
Every task in CumulusCI is a subclass of the BaseTask class. BaseTask is where much of the scaffolding is set up for you. At a high level, BaseTask includes:
- Support for specifying
task_options
and processing them via the_init_options
method. - Access to the project config as
self.project_config
from within the task and to the org config asself.org_config
for tasks that require one - A preconfigured Python logger instance available as
self.logger
- A
_run_task
method where classes implement their business logic - A framework for handling polling such as checking the status of a remote async job like a Metadata API deployment
In addition to BaseTask, CumulusCI includes a number of base task classes for more specific types of tasks:
- BaseSalesforceApiTask: For tasks that interact with the Salesforce REST API or Tooling API. Initializes pre-authenticated instances of the simple-salesforce Python library as
self.sf
andself.tooling
- BaseGithubTask: For tasks that interact with the GitHub API. Initializes a pre-authenticated instance of the github3.py Python libary as
self.github
- BaseMarketingCloudTask: For tasks that interact with the Marketing Cloud API. Provides the Marketing Cloud configuration and credentials as
self.mc_config
- BaseMetadataETLTask: Write metadata transformations that retrieve, transform, then deploy metadata. Most custom Metadata ETL tasks should subclass BaseMetadataSynthesisTask, BaseMetadataTransformTask, or MetadataSingleEntityTransformTask
While the logic of tasks is implemented in Python, the cumulusci.yml
file is used to wire those Python task classes into your project’s configuration. In each task definition, the class_path
provides the Python path to the class that implements the task’s logic. CumulusCI automatically adds the project’s repository root to the PYTHONPATH allowing for custom tasks to live in the project’s repository.
Now that we have an overview of the task framework, let’s dive into some examples to see it in action.
Example 1: Replace a String with Org Username
This real world use case was mentioned in the CumulusCI group on the Trailblazer Community by a user who needed to deploy an authProvider that has the target org’s username.
In this example, we’ll be extending CumulusCI’s built in FindReplace task which already contains much of the logic we need. The task accepts task options for find
, replace
, path
, and file_pattern
and will replace all instances of find
with the value of replace
in all files matching the path
and file_pattern
.
Since the FindReplace task provides all the replacement logic we need, all we need to do is create a custom task that dynamically sets the replace
value with the target org’s username. However, FindReplace is set up as a local task that doesn’t need a target org to run so we need the custom task to require a target org.
Step 1: Create the custom task in Python
Create a file called tasks/util.py
in the root of your repository containing
Let’s review the Python code:
- Line 1 imports the FindReplace task class from CumulusCI
- Line 4 creates a new class called FindReplaceUsername based off of FindReplace
- Line 5 configures the task to require a target org
- Line 7overrides the
_init_options
method to add custom logic to setting up the task’s options - Line 9runs the inherited logic from FindReplace._init_options
- Line 11 sets the
replace
option’s value to the target org’s username, found viaself.org_config.username
Step 2: Configure the task in cumulusci.yml
Now that we have the task implemented in Python, we need to add it as a task to the project’s config. Add the following to the tasks:
section of your CumulusCI.yml file
Step 3: Run It
You can verify that the task was successfully configured using cci
in a command prompt:
Example 2: Query the Tooling API
In this fictional use case, we want to query the Tooling API to get the info on all record types for a given object so that the values can be used in a later task in a flow. We’ll be creating a new task named get_record_types
with a task class named GetRecordTypes
that extends BaseSalesforceApiTask
to interact with the Tooling API.
Step 1: Create the custom task in Python
Create the file tasks/tooling.py
with the following code:
Let’s briefly review the Python code:
- Line 1 imports the BaseSalesforceApiTask class from CumulusCI
- Line 4 creates a new task class named
GetRecordTypes
which subclassesBaseSalesforceApiTask
- Lines 5–10 set up a single task option called
object
to receive the object name to list record types for - Lines 12–16 extend the
_init_options
task to add SOQL injection protection to theobject
option - Lines 20–23 use the pre-configured
sf.tooling
instance of the simple-salesforce Python library to query all record types for the object - Lines 24–26 use the pre-configured logger to output information to the console
- Line 34 adds the dictionary of record types to the pre-configured
self.return_values
dictionary used to pass values from one task to another in a flow
Step 2: Configure the task in cumulusci.yml
Add the following to the tasks:
section of your CumulusCI.yml file
Step 3: Run It
In a command prompt:
Should You Contribute Your Custom Task?
Since CumulusCI is open source, you should consider if the custom task you’ve written is specific to your project or if it is generically useful to other Salesforce developers. If it’s the latter, you can enhance CumulusCI by contributing your new task. While contributing may require a bit more work up front like writing test cases for your custom task, by contributing you are improving life for other Salesforce developers and get your task and its test cases running as part of CumulusCI’s builds.
Conclusion
Hopefully these two examples help illustrate the power of custom Python tasks in CumulusCI and how much of the heavy lifting CumulusCI’s task framework does for you. Now that you know how to write custom tasks for CumulusCI, what will you automate next?
Interested in exploring what CumulusCI can do to improve your development to delivery lifecycle? Book a free one-hour consultation at https://calendly.com/muselab