Take about orphan steps, fun with pipeline syntax 😸

This commit is contained in:
R Tyler Croy 2020-11-09 21:09:58 -08:00
parent ce284ffb0b
commit 8da5441e6d
No known key found for this signature in database
GPG Key ID: E5C92681BEF6CEA2
1 changed files with 112 additions and 0 deletions

View File

@ -0,0 +1,112 @@
---
layout: post
title: Orphan steps in Otto Pipeline
tags:
- otto
- rust
---
After [sketching out some Otto Pipeline
ideas](/2020/11/06/pipeline-syntax-for-otto.html) last week, I was fortunate
enough to talk to a couple peers in the Jenkins community about their pipeline
thoughts which led to a concept in Otto Pipelines: orphan steps. Similar to
Declarative jenkins Pipelines, my initial sketches mandated a series of `stage`
blocks to encapsulate behavior. [Steven
Terrana](https://github.com/steven-terrana), author of the [Jenkins Templating
Engine](https://www.jenkins.io/blog/2019/05/09/templating-engine/) made a
provocative suggestion: "**stages should be optional**."
It is always refreshing when somebody asks "but why have we always done it this
way?" and without a good answer, you find yourself asking the same question.
As best as I can remember, Declarative Pipeline requires the stages in order to
make rendering of pipelines prettier in [Blue
Ocean](https://www.jenkins.io/projects/blueocean/). The utility of stages for complex pipelines is pretty clear, but for the simpler pipelines, they end up adding unnecessary boilerplate. The following is the smallest possible Declarative Jenkins Pipeline needed to run `make`:
```groovy
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'make'
}
}
}
}
```
With the addition to orphan steps in Otto Pipelines, the smallest possible
invocation becomes:
```groovy
use stdlib
pipeline {
steps {
sh 'make'
}
}
```
The more I thought about orphan steps, the more I liked them. A stage can provide scoped options like
* Environment variables
* Runtime environment
* Credentials
* Gates
* Post-stage conditions
If you don't need any of the trappings or other capabilities that a stage
provides, why bother with the extra boilerplate! The pipeline execution is
already sequential, orphan steps will execute linearly just like any sequence
of stages would. An example:
```groovy
use stdlib
pipeline {
steps {
sh 'bundle exec rake spec package'
archive artifacts: 'pkg/*.gem', name: 'gems'
}
stage {
name = 'Publish to rubygems'
credentials = ['rubygems-token']
steps {
unarchive 'gems'
gemupload gems: 'pkg/*.gem'
}
}
steps {
echo '>> Any cleanup?'
}
}
```
Freeing my mind from the burden of considering "everything must be a stage"
allowed me to also consider other top level "verbs" that make sense within the
`pipeline [ }`. A rough list of what I am currently thinking would be:
* `steps`, orphan steps.
* `stage`, container of more configuration to execute steps.
* `fanout`, container of multiple stages to run in parallel, failing immediately if a branch fails.
* `parallel`, container of multiple stages to run in parallel.
* `confirm`, prompt a user to confirm that the pipeline can continue.
* `prompt`, prompt the user for a parameter(s) which can be saved into a variable.
* `rollback`, execute some `steps` if some event indicating a failure is fired in the Otto event system.
Quoting Steven again: "it's better to have different names for different
behaviors, rather than a bunch of configuration options." [This
gist](https://gist.github.com/rtyler/0f7878bb3116b28e74afaa72dc9fc0b1) contains
a rough sketch of what some of those verbs would look like.
At this point I am quite excited by the aesthetics of the pipeline with these
rich descriptive blocks. This approach doesn't result in significantly more
parser and implementation work, but it does have the potential to make Otto
Pipelines more clear in their behavior, which is fantastic.
The syntax shared above is far from final, but I cannot wait to get some of
this implemented to try out with real workloads!