CumulusCI’s Task Framework

Jason Lantz
5 min readOct 3, 2022

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 as self.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 and self.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 via self.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 subclasses BaseSalesforceApiTask
  • 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 the object 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

FindReplaceUsername custom task example

--

--

Jason Lantz

Founder@MuseLab, creator of CumulusCI, former Sr. Dir Release Engineering for Salesforce,org