Actions and Action Sets
In Expeditor, an action is a reference to a discrete unit of logic that contains the pattern for how to accomplish a specific task. Actions help ensure that common processes (e.g. bumping the version or managing the changelog) are handled consistently across our wide variety of projects.
Most of the actions in Expeditor are related to the release life-cycle of GitHub software projects. As such, these actions act not only as the tools of automating those processes but documenting them as well. An interested party can look at the Expeditor configuration and be able to get an understanding of the release processes for a project by evaluating the “when, then” subscriptions.
The structure of an action
An action consists of three elements:
- The action type
- The action name
- The action filters
Let’s start by focusing on the first two, the action type and the action name, as those form the foundation of what an action is. You can think of an action as a method (the action type) that only accepts a single parameter (the action name) separated by a colon. Here are some examples that we’ll continue to use throughout this document.
Action | Action Type | Action Name |
---|---|---|
built_in:bump_version | built_in | bump_version |
bash:.expeditor/update_version.sh | bash | .expeditor/bump_version.sh |
built_in:update_changelog | built_in | update_changelog |
trigger_pipeline:omnibus/release | trigger_pipeline | omnibus/release |
A built-in action (i.e. an action with the type built_in) is a class of action that codifies a common, repeatable pattern that is leveraged across a wide variety of projects — in comparison to action types like bash or trigger_pipeline which are far more generic. Please check out the full list of supported actions.
The final element of an action is its action filters. An action filter is configuration that can be applied to an action to indicate whether or not the action should be executed — given the context of the workload to which it is responding. More details about action filters are covered in their reference documentation.
Organizing actions into action sets
An action set is a collection of one or more actions that are executed against a workload as part of a subscription. Let’s organize the actions we introduced in the section above into an action set that is responding to a familiar workload: pull_request_merged:{{github_repo}}:{{release_branch}}:*
.
subscriptions:
- workload: pull_request_merged:{{github_repo}}:{{release_branch}}:*
actions:
- built_in:bump_version
not_if_labels:
- "Expeditor: Skip Version Bump"
- bash:.expeditor/update_version.sh
only_if: built_in:bump_version
- built_in:update_changelog
- trigger_pipeline:omnibus/release
In this example, we’ve introduced both the concept of the action set and some examples of action filters. As mentioned above, action filters allow us to specify if an action should occur, tacking on the concept of “if” to our “when, then” framework.
Let’s take a moment to break down what exactly our action set is doing and think about it in terms of “when, then, if.”
When any pull request is merged into my project, then …
- Bump the version of our software product but not if the Expeditor: Skip Version Bump GitHub label is applied to the pull request.
- Execute the .expeditor/update_version.sh bash script only if we’ve successfully bumped the version.
- Update the changelog
- Trigger the omnibus/release pipeline
Looking at this example, you may realize the addition of action filters makes things a bit more difficult to reason about — this is absolutely true! That is why we have the following suggestions and guidelines.
- Keep the number of actions in an action set to a minimum. The more actions you have, the more likely you are to need action filters to control behavior, and the more complex your subscription becomes. We recommended chaining together subscriptions if the cyclomatic complexity of your action set gets too high.
- Expeditor configuration is YAML, take advantage of comments. Remember, the .expeditor/config.yml file is not just configuration but also your documentation. Please use comments and white space as you see fit to increase the readability.
Understanding the different phases of the action set
There are three phases to the execution of an action set:
- pre-commit. Execute pre-commit actions, which modify files that are committed directly to the release branch.
- commit. Commit any modified files directly to the release branch, optionally create a tag, and push everything to GitHub. If the pre-commit actions do not modify any files, this phase is skipped.
- post-commit. Execute post-commit actions that depend on the results of the commit phase (including the git tag) having been pushed to GitHub.
All actions are will default to either the pre-commit or post-commit phase depending on which phase makes the most sense for their use case. The default value for every action is documented in the action reference documentation.
Here is how our example action set breaks out into pre- and post-commit actions:
Phase | Actions |
---|---|
pre-commit | built_in:bump_version bash:.expeditor/update_version.sh built_in:update_changelog |
commit | Commit the files modified by pre-commit actions to the release branch Tag the commit (if appropriate) Push to GitHub |
post-commit | trigger_pipeline:omnibus/release |
Let’s walk through our full action set assuming that our action filters resolve such that all actions are going to be executed.
pre-commit
- built_in:bump_version: Bump the patch version of our application. For our example, we’ll assume we’re bumping from
1.0.0
to1.0.1
. - bash:.expeditor/update_version.sh: Execute the
.expeditor/update_version.sh
bash script, located in the project’s GitHub repository. In most cases, iterations of this script use something likesed
to replace references of the old1.0.0
version to the new1.0.1
version. - built_in:update_changelog: Update our changelog with details of the pull request.
commit
- Commit the files modified as part of the pre-commit actions (e.g.
VERSION
,CHANGELOG.md
, etc) directly to the the release branch. - Tag the commit with the version number, typically in the form of
v1.0.1
depending on your configuration. - Push both the commit and the tag to GitHub.
post-commit
- trigger_pipeline:omnibus/release: Trigger the
omnibus/release
pipeline in Buildkite.