Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,17 @@ The demo shows:

- creating a lifecycle request
- building a deterministic plan from a workflow definition (`.psd1`)
- executing the plan using built-in steps (and optionally a host-provided step registry for extensions)
- executing the plan using built-in steps and a mock provider

By default, the demo runs **Mock workflows** that work out-of-the-box without external systems. The examples folder also includes **Live workflows** that demonstrate real-world scenarios with Active Directory and Entra ID, but these require the corresponding infrastructure and provider modules.

The execution result buffers all emitted events in `result.Events`. Hosts can optionally stream events live
by providing `-EventSink` as an object implementing `WriteEvent(event)`.

Next steps:

- Documentation entry point: `docs/index.md`
- Workflow samples: `examples/workflows/`
- Workflow samples: `examples/workflows/` (organized by category: mock, live, templates)
- Repository demo: `examples/Invoke-IdleDemo.ps1`
- Pester tests: `tests/`

Expand Down
52 changes: 40 additions & 12 deletions examples/Invoke-IdleDemo.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ param(
[Parameter(ParameterSetName = 'Run')]
[switch]$All,

[Parameter(ParameterSetName = 'List')]
[Parameter(ParameterSetName = 'Run')]
Comment thread
blindzero marked this conversation as resolved.
[ValidateSet('Mock', 'Live', 'Templates', 'All')]
Comment thread
blindzero marked this conversation as resolved.
[string]$Category = 'Mock',
Comment thread
blindzero marked this conversation as resolved.

[Parameter(ParameterSetName = 'Run')]
[ValidateRange(1, 50)]
[int]$Repeat = 1,
Expand Down Expand Up @@ -125,21 +130,41 @@ function Get-IdleLifecycleEventFromWorkflowName {
}

function Get-DemoWorkflows {
param(
[Parameter()]
[ValidateSet('Mock', 'Live', 'Templates', 'All')]
[string]$Category = 'Mock'
)

$workflowDir = Join-Path -Path $PSScriptRoot -ChildPath 'workflows'

if (-not (Test-Path -Path $workflowDir)) {
throw "Workflow directory not found: $workflowDir"
}

Get-ChildItem -Path $workflowDir -Filter '*.psd1' -File |
Sort-Object Name |
ForEach-Object {
[pscustomobject]@{
Name = $_.BaseName
Path = $_.FullName
File = $_.Name
}
$categories = if ($Category -eq 'All') {
@('mock', 'live', 'templates')
} else {
@($Category.ToLowerInvariant())
}

$workflows = foreach ($cat in $categories) {
$catDir = Join-Path -Path $workflowDir -ChildPath $cat
if (Test-Path -Path $catDir) {
Get-ChildItem -Path $catDir -Filter '*.psd1' -File |
Sort-Object Name |
ForEach-Object {
[pscustomobject]@{
Name = $_.BaseName
Path = $_.FullName
File = $_.Name
Category = $cat
}
}
}
}

return $workflows
}

function Select-DemoWorkflows {
Expand Down Expand Up @@ -204,20 +229,23 @@ Import-Module (Join-Path $PSScriptRoot '..\src\IdLE\IdLE.psd1') -Force -ErrorAct
Import-Module (Join-Path $PSScriptRoot '..\src\IdLE.Steps.Common\IdLE.Steps.Common.psd1') -Force -ErrorAction Stop
Import-Module (Join-Path $PSScriptRoot '..\src\IdLE.Provider.Mock\IdLE.Provider.Mock.psd1') -Force -ErrorAction Stop

$available = @(Get-DemoWorkflows)
$available = @(Get-DemoWorkflows -Category $Category)

if ($available.Count -eq 0) {
throw "No workflows found in 'examples/workflows'."
throw "No workflows found in 'examples/workflows' for category '$Category'."
}

if ($List) {
Write-DemoHeader "IdLE Demo – Available Examples"
$available | Select-Object Name, File | Format-Table -AutoSize
Write-DemoHeader "IdLE Demo – Available Examples (Category: $Category)"
$available | Select-Object Name, Category, File | Format-Table -AutoSize
return
}

$selected = @(Select-DemoWorkflows -AvailableWorkflows $available -ExampleNames $Example -AllWorkflows:$All)

# Note: Mock workflows use New-IdleMockIdentityProvider which works out-of-the-box.
# Live workflows require real providers and will fail if those providers are not available.
# To run Live workflows, users should modify this script to provide the necessary providers.
$providers = @{
Identity = New-IdleMockIdentityProvider
}
Expand Down
107 changes: 94 additions & 13 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,44 @@
# Examples

This folder contains runnable examples for IdLE.
This folder contains runnable examples for IdLE, organized into categories based on their requirements and intended use.

## Workflow Categories

### Mock
Workflows that run out-of-the-box with `IdLE.Provider.Mock`. These are fully functional demonstrations requiring no external systems.

**Prerequisites:**
- `IdLE` + `IdLE.Steps.Common`
- `IdLE.Provider.Mock`

**Workflows:**
- `joiner-minimal.psd1` — minimal workflow with a single EmitEvent step
- `joiner-minimal-ensureattribute.psd1` — demonstrates EnsureAttribute step
- `joiner-ensureentitlement.psd1` — demonstrates EnsureEntitlement step for group assignment
- `joiner-with-condition.psd1` — demonstrates conditional step execution
- `joiner-with-onfailure.psd1` — demonstrates OnFailureSteps for cleanup and notifications

### Live
Example workflows that require real providers and external systems (Active Directory, Entra ID, Entra Connect). These are intended as templates for production scenarios but cannot run without the necessary infrastructure.

**Prerequisites:**
- Real AD/Entra ID environment
- Provider modules: `IdLE.Provider.ActiveDirectory`, `IdLE.Provider.EntraID`, `IdLE.Provider.DirectorySync.EntraConnect`
- Appropriate authentication/credentials

**Workflows:**
- `ad-joiner-complete.psd1` — complete Active Directory joiner workflow
- `ad-mover-department-change.psd1` — Active Directory department change workflow
- `ad-leaver-offboarding.psd1` — Active Directory leaver offboarding workflow
- `entraid-joiner-complete.psd1` — complete Entra ID joiner workflow
- `entraid-mover-department-change.psd1` — Entra ID department change workflow
- `entraid-leaver-offboarding.psd1` — Entra ID leaver offboarding workflow
- `joiner-with-entraid-sync.psd1` — demonstrates cross-system workflow with AD and Entra ID sync

### Templates
Generic starting points for building custom workflows. These are structurally valid but not executed in CI.

(Currently empty - reserved for future templates)

## Run the demo

Expand All @@ -10,24 +48,50 @@ From the repository root:
pwsh -File .\examples\Invoke-IdleDemo.ps1
```

The demo:
By default, the demo runs **Mock** workflows only (deterministic, no external dependencies).

- builds a plan from a workflow (`.psd1`)
- executes the plan using mock providers
- prints step results and buffered events
### List available workflows

## Workflow samples
```powershell
# List Mock workflows (default)
.\examples\Invoke-IdleDemo.ps1 -List

Workflow samples are located in:
# List Live workflows
.\examples\Invoke-IdleDemo.ps1 -List -Category Live

- `examples/workflows/`
# List all workflows
.\examples\Invoke-IdleDemo.ps1 -List -Category All
```

Highlighted samples:
### Run specific workflows

- `joiner-minimal.psd1` — minimal workflow with a single EmitEvent step
- `joiner-with-condition.psd1` — demonstrates conditional step execution
- `joiner-ensureentitlement.psd1` — ensures a demo group assignment via the built-in EnsureEntitlement step
- `joiner-with-onfailure.psd1` — demonstrates OnFailureSteps for cleanup and notifications
```powershell
# Run specific Mock workflow by name
.\examples\Invoke-IdleDemo.ps1 -Example joiner-minimal

# Run all Mock workflows (default category)
.\examples\Invoke-IdleDemo.ps1 -All

# Run all workflows in a specific category
.\examples\Invoke-IdleDemo.ps1 -All -Category Live
```

**Note:** The demo script defaults to Mock workflows which work out-of-the-box. Live workflows can be executed via the demo script, but will fail if the required providers and infrastructure are not available. To run Live workflows, you must modify the demo script to provide the necessary real providers (see lines 246-248 in `Invoke-IdleDemo.ps1`).

### Interactive selection

If you run the script without parameters, it will interactively prompt you to select from available Mock workflows.

## How it works

The demo:

- validates the workflow using `Test-IdleWorkflow`
- builds a plan from the workflow (`.psd1`) using `New-IdlePlan`
- executes the plan using `Invoke-IdlePlan` with mock or real providers
- prints step results and buffered events

## Workflow structure

Workflows are **data-only** PSD1 files. A minimal workflow looks like:

Expand Down Expand Up @@ -56,3 +120,20 @@ $result.Events | Select-Object Type, StepName, Message
```

Hosts can optionally stream events live by providing `-EventSink` as an object implementing `WriteEvent(event)`.

## Workflow Matrix

| Workflow File | Category | Runnable with Mock | Required Providers | External Prerequisites |
|---------------|----------|--------------------|--------------------|------------------------|
| joiner-minimal.psd1 | Mock | ✅ Yes | Identity (Mock) | None |
| joiner-minimal-ensureattribute.psd1 | Mock | ✅ Yes | Identity (Mock) | None |
| joiner-ensureentitlement.psd1 | Mock | ✅ Yes | Identity (Mock) | None |
| joiner-with-condition.psd1 | Mock | ✅ Yes | Identity (Mock) | None |
| joiner-with-onfailure.psd1 | Mock | ✅ Yes | Identity (Mock) | None |
| ad-joiner-complete.psd1 | Live | ❌ No | Identity (AD) | Active Directory, credentials |
| ad-mover-department-change.psd1 | Live | ❌ No | Identity (AD) | Active Directory, credentials |
| ad-leaver-offboarding.psd1 | Live | ❌ No | Identity (AD) | Active Directory, credentials |
| entraid-joiner-complete.psd1 | Live | ❌ No | Identity (Entra ID) | Entra ID, credentials |
| entraid-mover-department-change.psd1 | Live | ❌ No | Identity (Entra ID) | Entra ID, credentials |
| entraid-leaver-offboarding.psd1 | Live | ❌ No | Identity (Entra ID) | Entra ID, credentials |
| joiner-with-entraid-sync.psd1 | Live | ❌ No | Identity (AD), Cloud (Entra ID), DirectorySync | AD, Entra ID, Entra Connect, credentials |
1 change: 1 addition & 0 deletions examples/workflows/templates/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Templates directory
37 changes: 37 additions & 0 deletions tests/ImportIdLE.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Set-StrictMode -Version Latest

BeforeAll {
. (Join-Path $PSScriptRoot '_testHelpers.ps1')
$repoRoot = Get-RepoRootPath
$importScript = Join-Path -Path $repoRoot -ChildPath 'tools/import-idle.ps1'
}

Describe 'Import-IdLE helper script' {
It 'import-idle.ps1 script exists' {
$importScript | Should -Exist
}

It 'import-idle.ps1 finds workflows in subdirectories' {
# The script should find workflows in examples/workflows/mock, live, and templates subdirectories
# This test validates that the script can discover workflows after the directory restructuring

$workflowDir = Join-Path -Path $repoRoot -ChildPath 'examples/workflows'

# Verify workflows exist in subdirectories
$mockWorkflows = Get-ChildItem -Path (Join-Path $workflowDir 'mock') -Filter '*.psd1' -File -ErrorAction SilentlyContinue
$liveWorkflows = Get-ChildItem -Path (Join-Path $workflowDir 'live') -Filter '*.psd1' -File -ErrorAction SilentlyContinue

$mockWorkflows | Should -Not -BeNullOrEmpty
$liveWorkflows | Should -Not -BeNullOrEmpty

# Verify the script logic for finding workflows recursively
$allWorkflows = Get-ChildItem -Path $workflowDir -Filter '*.psd1' -File -Recurse
$allWorkflows | Should -Not -BeNullOrEmpty
$allWorkflows.Count | Should -BeGreaterThan 5
}

It 'import-idle.ps1 executes without errors' {
# Execute the import script and verify it completes successfully
{ & $importScript -ErrorAction Stop } | Should -Not -Throw
}
}
93 changes: 88 additions & 5 deletions tests/WorkflowSamples.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,96 @@ BeforeAll {
$workflowsPath = Join-Path -Path (Get-RepoRootPath) -ChildPath 'examples/workflows'
}

Describe 'Example workflows' {
It 'All workflow PSD1 files validate' {
$files = Get-ChildItem -Path $workflowsPath -Filter '*.psd1' -File
$files | Should -Not -BeNullOrEmpty
Describe 'Mock example workflows' {
BeforeAll {
$mockWorkflowsPath = Join-Path -Path $workflowsPath -ChildPath 'mock'
$mockWorkflows = Get-ChildItem -Path $mockWorkflowsPath -Filter '*.psd1' -File -ErrorAction SilentlyContinue
}

It 'Mock workflow directory exists' {
$mockWorkflowsPath | Should -Exist
}

It 'Mock workflows exist' {
$mockWorkflows | Should -Not -BeNullOrEmpty
}

It 'All mock workflows validate with Test-IdleWorkflow' {
foreach ($file in $mockWorkflows) {
{ Test-IdleWorkflow -WorkflowPath $file.FullName } | Should -Not -Throw
}
}

It 'All mock workflows can create a plan with Mock provider' {
$providers = @{
Identity = New-IdleMockIdentityProvider
}

foreach ($file in $mockWorkflows) {
$workflow = Import-PowerShellDataFile -Path $file.FullName
$lifecycleEvent = if ($workflow.ContainsKey('LifecycleEvent')) { $workflow.LifecycleEvent } else { 'Joiner' }
$request = New-IdleLifecycleRequest -LifecycleEvent $lifecycleEvent -Actor 'test-user'

{ New-IdlePlan -WorkflowPath $file.FullName -Request $request -Providers $providers } | Should -Not -Throw
}
}

It 'All mock workflows execute successfully with Mock provider' {
$providers = @{
Identity = New-IdleMockIdentityProvider
}

foreach ($file in $mockWorkflows) {
$workflow = Import-PowerShellDataFile -Path $file.FullName
$lifecycleEvent = if ($workflow.ContainsKey('LifecycleEvent')) { $workflow.LifecycleEvent } else { 'Joiner' }
$request = New-IdleLifecycleRequest -LifecycleEvent $lifecycleEvent -Actor 'test-user'

$plan = New-IdlePlan -WorkflowPath $file.FullName -Request $request -Providers $providers
$result = Invoke-IdlePlan -Plan $plan -Providers $providers

$result.Status | Should -Be 'Completed' -Because "Mock workflow '$($file.Name)' should complete successfully"
}
}
}

Describe 'Live example workflows' {
BeforeAll {
$liveWorkflowsPath = Join-Path -Path $workflowsPath -ChildPath 'live'
$liveWorkflows = Get-ChildItem -Path $liveWorkflowsPath -Filter '*.psd1' -File -ErrorAction SilentlyContinue
}

It 'Live workflow directory exists' {
$liveWorkflowsPath | Should -Exist
}

It 'Live workflows exist' {
$liveWorkflows | Should -Not -BeNullOrEmpty
}

foreach ($file in $files) {
It 'All live workflows validate with Test-IdleWorkflow' {
foreach ($file in $liveWorkflows) {
{ Test-IdleWorkflow -WorkflowPath $file.FullName } | Should -Not -Throw
}
}
}

Describe 'Template example workflows' {
BeforeAll {
$templatesWorkflowsPath = Join-Path -Path $workflowsPath -ChildPath 'templates'
$templateWorkflows = Get-ChildItem -Path $templatesWorkflowsPath -Filter '*.psd1' -File -ErrorAction SilentlyContinue
}

It 'Templates workflow directory exists' {
$templatesWorkflowsPath | Should -Exist
}

It 'All template workflows validate with Test-IdleWorkflow (if any exist)' {
if ($templateWorkflows) {
foreach ($file in $templateWorkflows) {
{ Test-IdleWorkflow -WorkflowPath $file.FullName } | Should -Not -Throw
}
} else {
Set-ItResult -Skipped -Because 'No template workflows exist yet'
}
}
}
Loading