Skip to content

Workflow Examples

Curated examples that progress from simple to advanced. Each example demonstrates specific workflow features and targets realistic network automation scenarios.

All example files live in the examples/ directory. They are schema-validated in CI.

Documentation markers

The example files contain # --8<-- [start:...] / # --8<-- [end:...] comments. These are snippet markers used by the documentation system to include specific sections. They are valid YAML comments and do not affect the workflow definition.

Runnable vs. illustrative examples

The Hello World and Show Version examples use function blocks with real Python implementations in the Worker SDK (examples/getting-started/). The Config Backup example uses ping and configBackup which also have SDK implementations (examples/ping/, examples/use-cases/config_backup/). The remaining intermediate and advanced examples use illustrative function block names to demonstrate workflow patterns. To run these workflows, implement the referenced function blocks using the Worker SDK.

Error handling in production workflows

Several examples use continueOnError: true to skip failing steps gracefully. In production workflows, always consider your error handling strategy: continueOnError is useful for best-effort operations (e.g., reachability checks), but critical steps should fail the workflow so operators are alerted. See Conditions and Assertions for the full error handling toolkit.

Beginner

Hello World

The simplest possible workflow -- one function block, one step:

# A minimal workflow that runs a single function block.
# Uses seedEntity: global so it works without CMS -- ideal for getting started.
label: hello_world
name: hello_world
package: wf.example.neops.io
majorVersion: 1
minorVersion: 0
patchVersion: 0
seedEntity: global
type: workflow

steps:
  - type: functionBlock
    label: echo
    functionBlock: "fb.examples.neops.io/echo:1.0.0"
    parameters:
      message: "Hello from the neops workflow engine!"

Concepts: seedEntity: global, steps, function block reference, parameter passing.


Show Version

Collect software version from all devices -- a pure (read-only) workflow:

# Collects version information from all devices and stores it as a fact.
# Demonstrates a pure (read-only) workflow -- the simplest device-scoped example.
label: show_version
name: show_version
package: wf.example.neops.io
majorVersion: 1
minorVersion: 0
patchVersion: 0
seedEntity: device
type: workflow
description: "Collect software version from all devices in scope"

steps:
  - type: functionBlock
    label: get_version
    functionBlock: "fb.examples.neops.io/show_version:1.0.0"
    runOn: device

Concepts: seedEntity: device, runOn, description, pure function block reference.


Intermediate

Config Backup with Reachability Check

Back up running config, but skip unreachable devices:

# Backs up running configuration from devices, skipping devices
# that are not reachable. Demonstrates conditions and JMESPath.
label: config_backup
name: config_backup
package: wf.example.neops.io
majorVersion: 1
minorVersion: 0
patchVersion: 0
seedEntity: device
type: workflow
description: "Backup running config from reachable devices"

steps:
  - type: functionBlock
    label: check_reachability
    functionBlock: "fb.examples.neops.io/ping:1.0.0"
    runOn: device
    parameters:
      host: "{{ context.device.ip }}"
    continueOnError: true

  - type: functionBlock
    label: backup_config
    functionBlock: "fb.examples.neops.io/configBackup:1.0.0"
    runOn: device
    condition:
      type: jmes
      jmes: "{{ check_reachability.result.success == true }}"
      description: "Only back up reachable devices"
    parameters:
      backup_type: "running"

Concepts: continueOnError, conditions (condition.jmes), referencing previous step results ({{ check_reachability.result.success }}).


Interface Management

Enable interfaces based on CDP neighbor checks. Demonstrates acquire, assertions, retry, and multi-entity execution:

# Enables or disables interfaces based on CDP neighbor checks.
# Demonstrates acquire, conditions, assertions, and multi-step workflows.
label: interface_management
name: interface_management
package: wf.example.neops.io
majorVersion: 1
minorVersion: 0
patchVersion: 0
seedEntity: device
type: workflow
description: "Enable/disable interfaces based on CDP neighbor state"

acquire:
  - type: expansion
    entity: interface
    expandFromSeedEntity: true
    description: "Load all interfaces for each device"

steps:
  - type: functionBlock
    label: show_cdp
    functionBlock: "fb.examples.neops.io/deviceShow:1.0.0"
    runOn: device
    parameters:
      command: "show cdp neighbors"

  - type: functionBlock
    label: check_neighbors
    functionBlock: "fb.examples.neops.io/genericJmesCheck:1.0.0"
    runOn: device
    parameters:
      expression: "length(show_cdp.result.data.output) > `0`"
    assert:
      - type: jmes
        jmes: "{{ show_cdp.result.success == true }}"
        description: "CDP show command must have succeeded"

  - type: functionBlock
    label: configure_interface
    functionBlock: "fb.examples.neops.io/deviceConfiguration:1.0.0"
    runOn: interface
    condition:
      type: jmes
      jmes: "{{ context.interface.state == 'DOWN' }}"
      description: "Only configure interfaces that are down"
    parameters:
      template: "interface {{ context.interface.name }}\n no shutdown"
    retryConfig:
      maxRetries: 2
      delay: 10

Concepts: acquire (expansion), assert, condition on interface-level steps, retryConfig, runOn: interface.


Advanced

NetBox Synchronization

Sync device metadata from NetBox into the CMS. Demonstrates elastic queries and embedded workflows:

# Synchronizes device data from NetBox into the neops CMS.
# Demonstrates elastic acquire queries, embedded workflows,
# and multi-step data pipelines.
label: netbox_sync
name: netbox_sync
package: wf.example.neops.io
majorVersion: 1
minorVersion: 0
patchVersion: 0
seedEntity: device
type: workflow
description: "Sync device metadata from NetBox to neops CMS"

acquire:
  - type: elastic
    entity: device
    query: "facts.netbox_id: *"
    assertNotEmpty: true
    description: "Select devices that have a NetBox ID"

steps:
  - type: functionBlock
    label: fetch_netbox
    functionBlock: "fb.examples.neops.io/netboxFetch:1.0.0"
    runOn: device
    parameters:
      netbox_id: "{{ context.device.facts.netbox_id }}"

  - type: workflow
    label: update_metadata
    name: update_metadata
    steps:
      - type: functionBlock
        label: update_groups
        functionBlock: "fb.examples.neops.io/netboxGroupUpdate:1.0.0"
        runOn: device
        parameters:
          site: "{{ fetch_netbox.result.data.site }}"
          role: "{{ fetch_netbox.result.data.role }}"

      - type: functionBlock
        label: update_facts
        functionBlock: "fb.examples.neops.io/netboxFactsUpdate:1.0.0"
        runOn: device
        parameters:
          netbox_data: "{{ fetch_netbox.result.data }}"

Concepts: Elastic acquire queries, assertNotEmpty, embedded workflow step, multi-step data pipeline.


NAC Rollout

Roll out 802.1X Network Access Control to switch access ports. Demonstrates a complete multi-phase workflow with pre-checks, conditional configuration, and post-verification:

# Rolls out 802.1X NAC configuration to switch interfaces.
# Demonstrates embedded workflows, Jinja templating,
# conditional success/failure paths, and retry/rollback patterns.
label: nac_rollout
name: nac_rollout
package: wf.example.neops.io
majorVersion: 1
minorVersion: 0
patchVersion: 0
seedEntity: group
type: workflow
description: "Roll out 802.1X NAC to switch access ports"

acquire:
  - type: expansion
    entity: device
    expandFromSeedEntity: true
  - type: expansion
    entity: interface
    expandFromSeedEntity: true

steps:
  - type: workflow
    label: pre_checks
    name: pre_checks
    steps:
      - type: functionBlock
        label: verify_connectivity
        functionBlock: "fb.examples.neops.io/ping:1.0.0"
        runOn: device
        parameters:
          host: "{{ context.device.ip }}"

      - type: functionBlock
        label: collect_facts
        functionBlock: "fb.examples.neops.io/facts:1.0.0"
        runOn: device
        parameters:
          commands:
            - "show dot1x all"
            - "show authentication sessions"

  - type: functionBlock
    label: apply_nac_config
    functionBlock: "fb.examples.neops.io/configureDevice:1.0.0"
    runOn: interface
    condition:
      type: jmes
      jmes: "{{ context.interface.facts.port_type == 'access' }}"
      description: "Only configure access ports"
    parameters:
      template: |
        interface {{ context.interface.name }}
         authentication port-control auto
         dot1x pae authenticator
    retryConfig:
      maxRetries: 3
      delay: 30

  - type: functionBlock
    label: verify_nac
    functionBlock: "fb.examples.neops.io/genericJmesCheck:1.0.0"
    runOn: device
    parameters:
      expression: "length(collect_facts.result.data.output) > `0`"

Concepts: Group-level seedEntity, multi-level acquire (device + interface expansion), embedded workflow for pre-checks, Jinja templating in parameters, interface-level conditions, retry configuration, post-deployment verification.


Getting and Using the Examples

The example workflow files live in the examples/ directory of the workflow engine repository.

Clone the repository:

git clone https://github.com/zebbra/neops-workflow-engine.git
cd neops-workflow-engine/examples

Deploy an example to the engine:

yq -o=json hello-world.workflow.yaml | \
  jq '{workflow: .}' | \
  curl -X POST http://localhost:3030/workflow-definition \
    -H "Content-Type: application/json" -d @-

Run the matching function blocks: The echo and show_version function blocks have real implementations in the Worker SDK's examples/getting-started/ directory. The echo FB is the simplest possible function block -- it returns its input parameters as results, making it ideal for verifying your setup works end-to-end. Start a worker with:

cd /path/to/neops-worker-sdk-py
URL_BLACKBOARD=http://localhost:3030 DIR_FUNCTION_BLOCKS=examples/getting-started uv run neops_worker

Validating Your Workflows

VS Code Schema Association

Add this to your VS Code settings.json to get autocompletion and validation for workflow YAML files:

{
  "yaml.schemas": {
    "./schema/RootWorkflow.schema.json": "*.workflow.yaml"
  }
}

Command-Line Validation

Validate a workflow file against the schema:

npx ajv validate -s schema/RootWorkflow.schema.json -d my-workflow.workflow.yaml --spec=draft7 -c ajv-formats --strict=false

Or use the Makefile target:

make validate-examples