Universal Workflow Architecture¶
This section addresses the architecture of the Universal Workflow system. Operational methods for Universal Workflow are described at Universal Workflow Operations.
Universal Workflow implements the following goals:
- Provide accumulated best practices for managing systems through full life-cycle
- Provide all features of the RackN system without requiring cloned workflows and stages
- Provide profile-based configuration for altering a solid baseline
Universal Workflow is designed to allow for sets of workflows to be chained together to generate a dynamic system for building and maintaining systems.
Each Universal workflow follows a common format and layout. This layout allows for extension within the workflow and chaining among workflows. This is not required, but helps define a standard common form.
How Universal Workflow Relates to Pipelines and WorkOrders¶
Universal Workflow is the content-pack convention layer built on top of the pipeline execution primitives documented elsewhere in this section. It does not add new object types or new state machines to dr-provision; it defines a set of naming, layout, and chaining conventions that content packs use to compose the primitives into reusable applications.
The layering:
- Pipelines (see Pipeline Architecture) are the declarative objects that name a multi-phase execution chain and carry its Params. A "Universal Application" is the convention this doc describes for populating a Pipeline's Params so that the chain drives a machine to a specific end state.
- Workflow Mode (see Workflow Mode) is the machine-side execution model that walks the Tasks list a Universal workflow expands into. Every stage described under Layout below is a normal DRP Stage; every Task is a normal DRP Task.
- WorkOrder Workflow Mode (see
WorkOrder Workflow Mode) is the
queued, restartable execution model that wraps a pipeline run in a
WorkOrder object. The
universal-chain-workflowstage described under Workflow Chaining is what advancesMachine.Workflowbetween phases; those advances are what the server observes to appendPipelinePhaseRecordentries onto the active workflow-mode WorkOrder.
A reader building a new application should think of it this way: the pipeline primitives are the engine, and Universal Workflow is the convention that makes the engine easy to program.
Requirements¶
The universal workflow system requires some additional content packages to function. Minimally, universal requires:
- drp-community-content
- callback
- classify
- flexiflow
- validation
- task-library
Some workflows require additional elements, e.g. image-deploy. At the moment, these are defined as requirements, but may move into their owning content packages or plugins.
Universal Application¶
Universal Workflow revolves around the concept of an application. The application can be anything that a set of machines can be driven to. An application can be defined to install ESXi, install Ubuntu, or install Rocky Linux after configuring the hardware and afterwards add a webserver. The application is a helper to define a set of things to do. In almost all cases, these tasks are reused across different applications.
An application is expressed as a Pipeline that inherits from the
default Pipeline (see The default Pipeline)
and overrides one or more of the per-phase workflow lists. For
example, an "install Ubuntu 24.04" application is a Pipeline that:
- Inherits from
default(which already setsuniversal/discover-phase-workflowsto[universal-discover-core]anduniversal/os-phase-workflowsto[universal-linux-install]). - Optionally overrides
universal/os-phase-workflowsif a non-standard install path is needed. - Carries any distribution-specific params required by the standard Linux install (URL of the ISO, kickstart/autoinstall template selection, and so on).
The universal content pack ships pre-built application profiles for
common OSes (for example universal-application-ubuntu-24.04.1,
universal-application-rocky-9.1-dvd) that carry the distribution
params. To drive a machine to that end state, create a workflow-mode
WorkOrder against one of the shipped operational Blueprints — build
is the standard destructive path — with the target application
profile applied to the Machine. The Machine's WorkflowComplete
field becomes true when the pipeline reaches
universal-pipeline-complete.
Users can define their own applications and extensions as needed to deploy what they need. More on this in the Universal Workflow Operations section.
Per-Phase Workflow Layout¶
The five phases (prepare, discover, hardware, os, app) each contribute
two workflow objects to the chain — a -start and a -complete —
that bracket whatever workflows the operator lists in
universal/<phase>-phase-workflows. Both bookends follow a fixed
stage layout that enables tracking, validation, and extension without
requiring the operator to clone workflows.
The -start bookend layout:
universal-phase-context— readsuniversal/<phase>-phase-contextand switches the Machine's BaseContext for the phase. Ends with acontext:task entry that re-enters the runner under the new Context.- discover (per-phase inventory refresh) — pulls fresh Machine state before the phase runs.
universal-<phase>-start-callback— emits an event withcallback/action = universal-<phase>-startfor external notification and audit.universal-<phase>-pre-classification— runs the classifiers listed inuniversal/<phase>-pre-classification-list. Empty by default.universal-<phase>-pre-flexiflow— runs the tasks listed inuniversal/<phase>-pre-flexiflow. Operator extension point, empty by default.universal-chain-workflow— advances to the next entry in the phase's workflow list.complete-nobootenv— closes out the workflow without a BootEnv transition.
The -complete bookend layout:
discover-nobootenv— inventory refresh without a BootEnv change.universal-<phase>-post-flexiflow— operator extension point, empty by default.universal-<phase>-classification— runs the classifiers listed inuniversal/<phase>-classification-list.universal-<phase>-post-validation— runs the validation tasks listed inuniversal/<phase>-post-validation. Empty by default.universal-<phase>-complete-callback— emits an event withcallback/action = universal-<phase>-complete.universal-chain-workflow— advances to the next phase.complete-nobootenv.
The workflows between the bookends — the ones the operator lists in
universal/<phase>-phase-workflows — are ordinary DRP Workflows.
They need no special structure; the chain task treats each list entry
as an atomic step.
Extension points per phase¶
Every phase exposes the same set of extension params, letting the operator plug in work without cloning bookends:
universal/<phase>-pre-flexiflow— FlexiFlow tasks before the phase's core work.universal/<phase>-post-flexiflow— FlexiFlow tasks after the phase's core work.universal/<phase>-pre-classification-list— classifiers that run before the phase, typically used to derive param values that later stages depend on.universal/<phase>-classification-list— classifiers that run after the phase, typically used to record state or trigger downstream work.universal/<phase>-post-validation— validation tasks that gate the phase's completion.
All five lists default to empty on the default Pipeline, so a
freshly-installed universal content pack runs the bookends as
lightweight passes with no per-phase work of their own.
Classification¶
The classification stages that appear in each phase's bookends are
themselves configurable — they run whichever classifiers the operator
places in universal/<phase>-classification-list and
universal/<phase>-pre-classification-list. The discover phase ships
a default classifier that derives hardware and application-profile
hints from Machine state:
- set up hardware param — sets the
universal/hardwareparameter to a derived string. - set universal/application — converts the
rack/buildparameter to theuniversal/applicationparameter. - apply universal application profile — converts the
universal/applicationparameter into a profile name and applies that profile to the Machine. -
hardware profile search — tests a set of parameter combinations and applies matching hardware-specific profiles. The patterns tested, in order:
universal-bom-<rack/bom>-<universal/hardware>-<universal/application>universal-hw-<rack/bom>-<universal/hardware>-<universal/application>universal-bom-<universal/hardware>-<universal/application>universal-hw-<universal/hardware>-<universal/application>universal-bom-<universal/hardware>universal-hw-<universal/hardware>universal-bom-<universal/application>universal-hw-<universal/application>
All other phases' classifier lists default to empty. The classifier mechanism is the primary way universal content adapts a generic pipeline to a specific machine — hardware profile, application profile, OS-family profile — based on data collected during discover.
See Universal Workflow Operations for examples and usage.
Phase-Based Chaining¶
The other main goal of the universal workflow system is to allow for workflow chaining. Universal content walks a machine through the standard five-phase sequence
prepare → discover → hardware → os → app
then hands off to a terminal universal-complete workflow. Each phase
is composed of an ordered list of workflows, and the universal-chain-workflow
task advances the machine one workflow at a time until the phase is
exhausted, at which point it advances to the next phase.
The chain task and its two paths¶
The universal-chain-workflow task is the single mechanism responsible
for advancing Machine.Workflow. It has two implementations selected at
render time by the value of Machine.Pipeline:
ucw-pipe.{sh,py,ps1}.tmpl— the pipeline-driven path, used whenMachine.Pipelineis non-empty. This is the current preferred mechanism and the subject of this section.ucw-ua.{sh,py,ps1}.tmpl— the legacy application-map path, used whenMachine.Pipelineis empty. It preserves the older behaviour keyed offuniversal/applicationanduniversal/workflow-chain-mapfor content that has not yet been migrated to Pipelines.
The dispatch is a two-line check at the top of universal-chain-workflow:
{{ $tmpl := "ucw-pipe.sh.tmpl" -}}
{{ if eq .Machine.Pipeline "" -}}
{{ $tmpl = "ucw-ua.sh.tmpl" -}}
{{ end -}}
{{ .CallTemplate $tmpl . }}
New content should set Machine.Pipeline (or apply a workflow-mode
WorkOrder whose Blueprint sets a Pipeline) so the pipeline path is
selected.
State: which phase is active¶
Two params, both scoped to universal/, track progress through the
sequence:
universal/active-phase— the current phase name. Defaults toprepare. Reset topreparewhenuniversal-pipeline-startis the workflow that just finished.universal/phase-index— index into the active phase's workflow list. Defaults to-1. Reset to-1when the phase changes.
The chain task is the only writer of these two params. It reads them
at render time and writes updated values via drpcli machines set
<uuid> param universal/active-phase to <name> (and the same for
phase-index) as part of its output script.
Phase composition: the per-phase workflow lists¶
Each phase's content is defined by one param:
universal/prepare-phase-workflows
universal/discover-phase-workflows
universal/hardware-phase-workflows
universal/os-phase-workflows
universal/app-phase-workflows
Each is a list of workflow names. The chain expands a phase into the sequence
universal-<phase>-start
→ each workflow in universal/<phase>-phase-workflows, in order
→ universal-<phase>-complete
The -start and -complete bookends are inserted automatically by
the chain task's index arithmetic; the list itself contains only the
phase-specific work.
To include a phase in a Pipeline, set that phase's list on the
Pipeline (or on any Pipeline it inherits from, on the Blueprint, or
on the Machine directly). To exclude a phase entirely, set
universal/no-<phase> to true on the same object; the chain task
will skip both bookends and any list content for that phase.
The advance algorithm¶
On each render, the chain task decides the next value of
Machine.Workflow by the following logic. $wf below is the
workflow that just finished — i.e. the current value of
Machine.Workflow.
- If
$wf == "universal-pipeline-start", reset(active-phase, phase-index)to("prepare", -1). - Else if
$wf == "universal-pipeline-complete", hand off touniversal-completeand clear the machine'spipelineandpipeline-uuidMeta keys. Ifuniversal/no-complete-phaseistrue, hand off to""instead (the machine goes idle in place). - Else if
$wf == "universal-<active-phase>-complete", advanceactive-phaseto the next phase in the succession map and resetphase-index = -1. - Skip loop (up to 6 iterations): while
universal/no-<active-phase>istrueoruniversal/<active-phase>-phase-workflowsis empty, advance to the next phase. If we reachcomplete, scheduleuniversal-pipeline-complete. - Otherwise, increment
phase-indexand pick the next workflow:phase-index == 0→universal-<phase>-start1 ≤ phase-index ≤ len(list)→ the phase list entry atphase-index - 1phase-index > len(list)→universal-<phase>-complete
- Emit
drpcli machines workflow <uuid> <nextWorkflow>plus the twodrpcli machines set … param universal/active-phase | phase-indexcalls to persist the new state.
Step 2's Meta-key clearing is what closes out the pipeline run from the content side; the corresponding server-side cleanup on the WorkOrder is documented in WorkOrder Workflow Mode.
Per-phase Context switching¶
Each phase may run in its own Context (typically a container image),
independent of the phase that came before. Four params — one per
phase, except hardware — carry the Context name:
universal/prepare-phase-context
universal/discover-phase-context
universal/hardware-phase-context
universal/os-phase-context
universal/app-phase-context
All default to the empty string, meaning "run natively on the
machine." The universal-phase-context stage — first stage of every
universal-<phase>-start workflow — runs the
universal-phase-context-set task, which reads
universal/active-phase, looks up universal/<phase>-phase-context,
and either clears the Machine's BaseContext (empty value) or sets
it to the named Context (verifying with drpcli contexts show that
it exists). The stage then includes a context: task-list entry that
forces the runner to re-enter under the new Context before executing
the rest of the workflow.
The default Pipeline¶
The universal content pack ships a base Pipeline named default at
content/pipelines/default.yaml. It is the recommended parent for
custom Pipelines and establishes the standard five-phase chain.
Its Params:
| Param | Default | Meaning |
|---|---|---|
universal/prepare-phase-workflows |
[] |
Prepare phase is empty and skipped |
universal/discover-phase-workflows |
[universal-discover-core] |
Standard inventory + discovery |
universal/hardware-phase-workflows |
[universal-hardware, universal-burnin] |
Hardware configuration then burn-in; operators override per-vendor via child Pipelines |
universal/os-phase-workflows |
[universal-linux-install] |
Standard Linux install |
universal/app-phase-workflows |
[universal-runbook] |
Post-install customization via injected runbook tasks |
universal/no-complete-phase |
false |
Emit universal-complete at the end |
To build a new application, define a Pipeline whose Pipelines field
lists default and whose Params override only the phase lists it
needs to change. For example, a Pipeline that installs a webserver
after the standard Linux install would inherit everything from
default and set universal/app-phase-workflows to
[install-webserver].
Skip flags — universal/no-{prepare,discover,hardware,os,app} — let
an operational Pipeline turn off entire phases without editing the
inherited workflow lists. See the shipped Blueprints below for how
these compose.
Shipped operational Blueprints¶
The universal content pack ships four Blueprints that name the
default Pipeline and universal-pipeline-start as the entry
workflow. Together they cover the common machine-lifecycle actions:
build— full destructive build. SetsParams: {erase-disks-all: true}. Runs every phase from prepare through app.rebuild— same asbuild, intended for re-running on an already-provisioned machine. SetsParams: {erase-disks-all: true}.refresh— non-destructive OS reinstall. SetsParams: {erase-disk-os: true, universal/no-hardware: true}. Wipes only the OS disk and skips the hardware phase; useful for reinstalling an OS on a machine whose BIOS/RAID configuration is already correct.decommission— return to idle. SetsParams: {erase-disks-all: true, universal/no-os: true, universal/no-app: true}. Runs prepare/discover/hardware only.
To run one against a machine, create a workflow-mode WorkOrder
naming the target machine, Mode: workflow, and one of the four
Blueprints. See
WorkOrder Workflow Mode for the full
lifecycle.
Legacy Application-Map Chaining¶
For content that has not yet been migrated to Pipelines, the chain
task falls back to the older map-based chainer via
ucw-ua.{sh,py,ps1}.tmpl. In this path:
- The
universal/applicationparameter names the current application (e.g.centos-8,ubuntu-24.04.1). - The
universal/workflow-chain-mapparameter is a map keyed by workflow name that names the next workflow to run. universal/workflow-chain-index-overrideoverrides theuniversal/applicationkey used to select the map entry.universal/workflow-chain-overridereplaces the whole map with a direct name-to-name lookup.
There is a special case for the universal-hardware workflow: when
universal/maintenance-mode is set, the chain is forced to
universal-local so a hardware technician can operate on a machine
without triggering a reinstall. The chain unsets the maintenance
flag as it exits.
New content should not depend on this path — set Machine.Pipeline
and use the phase-based chaining described above.