Skip to content

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-workflow stage described under Workflow Chaining is what advances Machine.Workflow between phases; those advances are what the server observes to append PipelinePhaseRecord entries 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 sets universal/discover-phase-workflows to [universal-discover-core] and universal/os-phase-workflows to [universal-linux-install]).
  • Optionally overrides universal/os-phase-workflows if 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:

  1. universal-phase-context — reads universal/<phase>-phase-context and switches the Machine's BaseContext for the phase. Ends with a context: task entry that re-enters the runner under the new Context.
  2. discover (per-phase inventory refresh) — pulls fresh Machine state before the phase runs.
  3. universal-<phase>-start-callback — emits an event with callback/action = universal-<phase>-start for external notification and audit.
  4. universal-<phase>-pre-classification — runs the classifiers listed in universal/<phase>-pre-classification-list. Empty by default.
  5. universal-<phase>-pre-flexiflow — runs the tasks listed in universal/<phase>-pre-flexiflow. Operator extension point, empty by default.
  6. universal-chain-workflow — advances to the next entry in the phase's workflow list.
  7. complete-nobootenv — closes out the workflow without a BootEnv transition.

The -complete bookend layout:

  1. discover-nobootenv — inventory refresh without a BootEnv change.
  2. universal-<phase>-post-flexiflow — operator extension point, empty by default.
  3. universal-<phase>-classification — runs the classifiers listed in universal/<phase>-classification-list.
  4. universal-<phase>-post-validation — runs the validation tasks listed in universal/<phase>-post-validation. Empty by default.
  5. universal-<phase>-complete-callback — emits an event with callback/action = universal-<phase>-complete.
  6. universal-chain-workflow — advances to the next phase.
  7. 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/hardware parameter to a derived string.
  • set universal/application — converts the rack/build parameter to the universal/application parameter.
  • apply universal application profile — converts the universal/application parameter 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

Text Only
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 when Machine.Pipeline is 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 when Machine.Pipeline is empty. It preserves the older behaviour keyed off universal/application and universal/workflow-chain-map for content that has not yet been migrated to Pipelines.

The dispatch is a two-line check at the top of universal-chain-workflow:

Text Only
{{ $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 to prepare. Reset to prepare when universal-pipeline-start is the workflow that just finished.
  • universal/phase-index — index into the active phase's workflow list. Defaults to -1. Reset to -1 when 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:

Text Only
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

Text Only
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.

  1. If $wf == "universal-pipeline-start", reset (active-phase, phase-index) to ("prepare", -1).
  2. Else if $wf == "universal-pipeline-complete", hand off to universal-complete and clear the machine's pipeline and pipeline-uuid Meta keys. If universal/no-complete-phase is true, hand off to "" instead (the machine goes idle in place).
  3. Else if $wf == "universal-<active-phase>-complete", advance active-phase to the next phase in the succession map and reset phase-index = -1.
  4. Skip loop (up to 6 iterations): while universal/no-<active-phase> is true or universal/<active-phase>-phase-workflows is empty, advance to the next phase. If we reach complete, schedule universal-pipeline-complete.
  5. Otherwise, increment phase-index and pick the next workflow:
    • phase-index == 0universal-<phase>-start
    • 1 ≤ phase-index ≤ len(list) → the phase list entry at phase-index - 1
    • phase-index > len(list)universal-<phase>-complete
  6. Emit drpcli machines workflow <uuid> <nextWorkflow> plus the two drpcli machines set … param universal/active-phase | phase-index calls 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:

Text Only
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. Sets Params: {erase-disks-all: true}. Runs every phase from prepare through app.
  • rebuild — same as build, intended for re-running on an already-provisioned machine. Sets Params: {erase-disks-all: true}.
  • refresh — non-destructive OS reinstall. Sets Params: {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. Sets Params: {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/application parameter names the current application (e.g. centos-8, ubuntu-24.04.1).
  • The universal/workflow-chain-map parameter is a map keyed by workflow name that names the next workflow to run.
  • universal/workflow-chain-index-override overrides the universal/application key used to select the map entry.
  • universal/workflow-chain-override replaces 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.