Full CircleCI - manual triggers of scheduled workflows
by wurbanski • 04.10.2019 • 6 minutes
Using CircleCI is a fun trip, but there are some things which do not come easy when using this system. Sure, you get Windows, Linux and macOS build support in a SaaS service which is nice, but sometimes you might want to do something not readily supported by CircleCI that you got used to when using another CI system. I know I did.
This post is a companion to freshly created GitHub repository - circleci-playground, which is going to contain some of the hacks I'm proud(-ish) of and I think that might come of use to others. Feel free to reuse it!
Before we get to the first showcase, which is triggerring a scheduled workflow manually, bear with me for some introduction to triggers.
So, basic method of running a workflow in CircleCI is to assign it to a specific branch (or even all of them), optionally limit triggering to open Pull Requests and master
branch if you don't want to run too many jobs. There is even an API for that, which allows you to tell to trigger a workflow assigned to a specific branch. It's still labeled as preview, but don't worry, we're going to do something much crazier soon.
The way this API works, is that it mimics a webhook sent by github to CircleCI (I think it's an PushEvent webhook, but don't quote me on that :)). In return, CircleCI evaluates repository's config and triggers any workflows attached to the branch we specified. Voilà!
But there is more than one way to skin a cat. At some point, you've most likely used or wanted to use a scheduled workflow, which can be used to run some long tests each night or once a week, for example. There is a chance that this periodic workflow contains some specific jobs
layout, sometimes really convoluted. You create a cron schedule for it and are done with it, it runs accordingly and you're happy.
Unless you want to run it without waiting for the scheduled time.
Disclaimer: What I'm describing here is risky since we're going to use a API which, at the time of writing, is in preview state, so it might break at any time. But it may work like this for a long time still, so measure the risks you can take and be mindful when following my footsteps :).
If you define a scheduled workflow, you are setting it to trigger on a specific time and from a specific branch like this:
workflows:
version: 2
periodic:
triggers:
- schedule:
cron: "0 10 * * 5"
filters:
branches:
only:
- master
jobs:
- say_hi
- say_bye:
requires:
- say_hi
Now, I don't know how exactly this is handled by CircleCI but I can tell that, for sure, this is not going to be triggered by a push to GitHub nor a call to the aforementioned API. This would mean that there is no other way to run it that to wait for the time to come.
But here comes the part when features from experimental API v2 come into play.
In an effort to allow more dynamic changes to the pipelines, CircleCI introduces yet another set of parameters - pipeline parameters - which can be used to alter the config in runtime. Let's start with defining a parameter we're going to use to control our pipelines:
version: 2.1
parameters:
run_workflow:
type: boolean
default: false
Note that we have to use version: 2.1
here, which in itself is breaking ability to test your jobs before pushing the config (OK, at least making it harder...) while enabling reusable configs. Everything comes with a price, I guess...
Now, let's use our newly added parameter for more fun and profit.
Here we're cheating a little bit by creating an additional workflow, but I'll try to make it as painless as possible.
In order to actually manipulate our config, we're going to use when:
clause for conditional workflow scheduling based on our run_workflow
parameter:
workflows:
version: 2
periodic_manual:
when: << pipeline.parameters.run_workflow >>
jobs: *periodic
So now our periodic_manual
workflow will be considered to be run on every commit but will only be actually scheduled when run_workflow
is set to true
. Which for now isn't - remember that the default value is false
and we're not overriding it (yet). But before we get to do that... Did you notice something unusual in the YAML snippet above?
*periodic
is a dereferenced YAML anchor. It's the cheating part. We're using it here to follow the DRY rule by reusing our job definitions from periodic
workflow, defined like this:
workflows:
periodic:
triggers:
- schedule:
<snip>
jobs: &periodic
- say_hi
- say_bye:
requires:
- say_hi
To read more about using YAML anchors, you can go to learnxinyminutes, for example. They're a part of the YAML spec!
So the state of things for now is as follows:
run_workflow
exists to control whether we run our periodic
-like workflowperiodic_manual
(heh) workflow, which reuses the jobs chain from periodic
Let's tackle the last step :).
Last element of our puzzle is an API call to a new endpoint, available in API v2, which is trigger pipeline. Now you might be confused - I've talked about abother endpoint, which can be used for running pipelines from a branch! Well, I can't blame you for that, those endpoints seem to do a very similar job, but the difference is that this new v2 endpoint allows us to specify pipeline parameters as well as a target branch!
So, we can call this endpoint using a command such as:
curl -u ${CIRCLECI_TOKEN}: -X POST --header "Content-Type: application/json" -d '{
"branch": "dev",
"parameters": {
"run_workflow": true
}
}' https://circleci.com/api/v2/project/github/wurbanski/circleci-playground/pipeline
Like that, we're going to set our workflow-defining parameter run_workflow
to true
and trigger our periodic_manual
workflow on a branch named dev
. (Note the v2 part of the endpoint URL.)
Thus, by using three not-yet-officially-released features, we got a manual trigger of a scheduled workflow to work in CircleCI. Kind of. If you don't mind a little bit of cheating using anchors. And an additional workflow. Anyway...
If this writeup does not explain the solution well enough - let me know! But also have a look at this shiny repository, which contains all of the code from above in its natural habitat. Hope this helps (and the API does not break while we're using it!).
It's only fair for me to mention that I wouldn't have been able to come up with this solution without reading a blog post by labs42 regarding conditional workflows and how they can be used to mitigate another of non-supported use cases for CircleCI - handling monorepos. If you use monorepos with CircleCI - this is a must-read.