Figuring Out Spinnaker Pipeline Expressions
I’m using Spinnaker to manage microservice deployments in one of my projects. One of the nice features in Spinnaker is pipeline expressions - a way to do a little dynamic calculation based on the current state of the pipeline.
I’m somewhat new to Spinnaker and I just got through trying to figure out a way to do some conditional stage execution using pipeline expressions but found the learning curve was steep. I don’t plan on repeating everything already in the docs… but hopefully I can help you out.
The Layers: Spinnaker pipeline expressions use the Spring Expression Language syntax to expose the currently executing Spinnaker pipeline object model along with some whitelisted Java classes so you can make your pipeline stages more dynamic.
That’s a lot to build on: The Spinnaker object model, Spring Expression Language, and Java. If you’re, say, a Node or .NET developer, it’s a bit of an uphill battle.
Resources:
- Spinnaker Pipeline Expressions User Guide
- Spinnaker Pipeline Expressions Reference
- Spring Expression Language
- Spinnaker Pipeline Object Model (code)
- Spinnaker Pipeline
ExecutionStatus
(code)
The Data: The best way you can see what pipeline data you have available is to run a pipeline. Once you have that, expand the pipeline and click the “Source” link in the bottom right. That will get you a JSON document.
The JSON doc is an “execution.” In there you’ll see the “stages” each of which has a “context” object. This is the stuff you’ll get when you use the helper functions like #stage("My Stage")
- it’ll find the JSON stage in the execution with the appropriate name and expose the properties so you can query them.
Troubleshooting: There are two ways I’ve found to troubleshoot.
- Create a test pipeline. Actually just run the expression in a test pipeline. You’ll get the most accurate results from this, but you may not be able to test the queries or operations against a “real pipeline execution” this way.
- Use the REST API. Spinnaker has a REST API and one of the operations on a pipeline is
evaluateExpression
. This runs against already-complete executions but is the fastest way to iterate over most queries. I’ll show you how to use that.
If you want to use the REST API to test an expression, first find a pipeline execution you want to use for testing. As with earlier, go get the JSON doc for a pipeline run by clicking the “Source” link on a complete pipeline. Now look at the URL. It’ll look like http://spin-api.your.com/pipelines/01CMAJQ6T8XC0NY39T8M3S6906
. Grab that URL.
Now with your favorite tool (curl
, Postman, etc.)…
- Create a POST request to the
evaluateExpression
endpoint for that pipeline, like:http://spin-api.your.com/pipelines/01CMAJQ6T8XC0NY39T8M3S6906/evaluateExpression
- In the body of the request, send a single parameter called
expression
. The value ofexpression
should be the expression you want to evaluate, like${ 1 + 1 }
or whatever. - The response will come back with the result of the expression, like
{ result: 2 }
. If you get an error, read the message carefully! The error messages will usually help you know where to look to solve the issue. For example, if you see the errorFailed to evaluate [expression] EL1004E: Method call: Method length() cannot be found on java.util.ArrayList type
- you know that whatever you’re callinglength()
on is actually anArrayList
so go look at theArrayList
documentation and you’ll find out it’ssize()
notlength()
. The errors will help you search!
Example: The pipeline expression I wanted was to determine if any prior step in the pipeline had a failure. The expression:
${ #stage("My Stage").ancestors().?[status.isFailure() && name != "My Stage"].isEmpty() }
Let’s walk through how I got there.
${ ... }
wraps every pipeline expression in Spinnaker.#stage("...")
is a shortcut for getting a stage from the pipeline by name."My Stage"
is just the name of the stage running the expression. There isn’t a way I could find to access the current stage.#root
gives you the stage context but not the stage definition.ancestors()
is a method on the SpinnakerStage
object. Notice thatancestors()
returns all the ancestor stages and the current stage..?
is the Spring Expression Language collection selection operator. It runs the expression in the brackets and returns a filteredArrayList
.status
is a property on theStage
object and is of typeExecutionStatus
.isFailure()
is a method onExecutionStatus
that determines if the specific status is one of several that qualify as unsuccessful.name
is a property on theStage
object that has the human-readable name. This clause is how we exclude the current stage from the result set.isEmpty()
is a method on the JavaArrayList
class, which is what comes out of a SpEL collection selection.
How did I figure all that out? Lots of trial and error, lots of poking around through the source. That REST API evaluation mechanism was huge.