Target directory must exist with rendered template files
absent
Managed files must be removed from the target
Template Engines
Two template engines are supported:
Engine
Library
Default Delimiters
Description
go
Go text/template
{{ / }}
Standard Go templates
jet
Jet templating
[[ / ]]
Jet template language
The engine defaults to jet if not specified. Delimiters can be customized via left_delimiter and right_delimiter properties.
Properties
Property
Type
Required
Description
source
string
Yes
Source template directory path or URL
engine
string
No
Template engine: go or jet (default: jet)
skip_empty
bool
No
Skip empty files in rendered output
left_delimiter
string
No
Custom left template delimiter
right_delimiter
string
No
Custom right template delimiter
purge
bool
No
Remove files in target not present in source
post
[]map[string]string
No
Post-processing: glob pattern to command mapping
# Render configuration templates using Jet engine- scaffold:
- /etc/app:
ensure: presentsource: templates/appengine: jetpurge: true# Render with Go templates and custom delimiters- scaffold:
- /etc/myservice:
ensure: presentsource: templates/myserviceengine: goleft_delimiter: "<<"right_delimiter: ">>"# With post-processing commands- scaffold:
- /opt/app:
ensure: presentsource: templates/apppost:
- "*.go": "go fmt {}"
Apply Logic
┌─────────────────────────────────────────┐
│ Get current state via Status() │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Is current state desired state? │
└─────────────────┬───────────────────────┘
Yes │ No
▼ │
┌───────────┐ │
│ No change │ │
└───────────┘ │
▼
┌─────────────────────────┐
│ What is desired ensure? │
└─────────────┬───────────┘
│
┌───────────────┴───────────────┐
│ absent │ present
▼ ▼
┌───────────┐ ┌───────────┐
│ Noop? │ │ Noop? │
└─────┬─────┘ └─────┬─────┘
Yes │ No Yes │ No
▼ │ ▼ │
┌────────────┐│ ┌────────────┐│
│ Set noop ││ │ Set noop ││
│ message ││ │ message ││
└────────────┘│ └────────────┘│
▼ ▼
┌───────────────┐ ┌─────────────────────┐
│ Remove all │ │ Scaffold │
│ managed files │ │ (render templates) │
│ and empty dirs│ │ │
└───────────────┘ └─────────────────────┘
Idempotency
The scaffold resource determines idempotency by rendering templates in noop mode and comparing results against the target directory.
State Checks
Ensure absent: Target must not exist, or no managed files remain on disk (Changed and Stable lists empty). Purged files (files not belonging to the scaffold) do not affect this check.
Ensure present: The Changed list must be empty, and the Purged list must be empty when purge is enabled (all files are stable). When purge is disabled, purged files do not affect stability.
Decision Table
For ensure: absent, purged files never affect stability since they don’t belong to the scaffold. For ensure: present, purged files only affect stability when purge is enabled.
When ensure: absent, the Status method filters Changed and Stable lists to only include files that actually exist on disk, so the state reflects reality after removal rather than what the scaffold would create.
Desired
Target Exists
Changed Files
Purged Files
Purge Enabled
Stable?
absent
No
N/A
N/A
N/A
Yes
absent
Yes
None
Any
N/A
Yes (no managed files on disk)
absent
Yes
Some
Any
N/A
No (managed files remain)
present
Yes
None
None
Any
Yes
present
Yes
None
Some
No
Yes (purged files ignored)
present
Yes
None
Some
Yes
No (purge needed)
present
Yes
Some
Any
Any
No (render needed)
present
No
N/A
N/A
Any
No (target missing)
Source Resolution
The source property is resolved relative to the manager’s working directory when it is a relative path:
This allows manifests bundled with template directories to use relative paths. URL sources (with a scheme) are passed through unchanged.
Path Validation
Target paths (the resource name) must be:
Absolute (start with /)
Canonical (no . or .. components, filepath.Clean(path) == path)
Post-Processing
The post property defines commands to run on rendered files. Each entry is a map where the key is a glob pattern matched against the file’s basename and the value is a command to execute. Use {} as a placeholder for the file’s full path; if omitted, the path is appended as the last argument.
Post-processing runs immediately after each file is rendered. Validation ensures neither keys nor values are empty.
Noop Mode
In noop mode, the scaffold type queries the current state via Status() and reports what would change without modifying the filesystem. Neither Scaffold() nor Remove() are called.
For ensure: present, the affected count is the number of changed files plus purged files (when purge is enabled). For ensure: absent, the affected count is the number of changed and stable files plus purged files (when purge is enabled).
Desired
Affected Count
Message
present
Changed + Purged (if purge enabled)
Would have changed N scaffold files
absent
Changed + Stable + Purged (if purge enabled)
Would have removed N scaffold files
Changed is set to true only when the affected count is greater than zero. When the resource is already in the desired state, Changed is false and NoopMessage is empty.
Desired State Validation
After applying changes (in non-noop mode), the type verifies the scaffold reached the desired state by checking the changed and purged file lists. If validation fails, ErrDesiredStateFailed is returned.
Subsections of Scaffold Type
Choria Provider
This document describes the implementation details of the Choria scaffold provider for rendering template directories using the choria-io/scaffold library.
Provider Selection
The Choria provider is the default and only scaffold provider. It is always available and returns priority 1 for all scaffold resources.
Operations
Scaffold (Render Templates)
Process:
Check if target directory exists
Configure scaffold with source, target, engine, delimiters, post-processing, and skip_empty settings
Create scaffold instance using the appropriate engine (scaffold.New() for Go, scaffold.NewJet() for Jet)
Call Render() (real mode) or RenderNoop() (noop mode)
Categorize results into changed, stable, and purged file lists
Scaffold Configuration:
Config Field
Source Property
Description
TargetDirectory
Name
Target directory for rendered files
SourceDirectory
Source
Source template directory
MergeTargetDirectory
(always true)
Merge into existing target directory
Post
Post
Post-processing commands
SkipEmpty
SkipEmpty
Skip empty rendered files
CustomLeftDelimiter
LeftDelimiter
Custom template left delimiter
CustomRightDelimiter
RightDelimiter
Custom template right delimiter
Engine Selection:
Engine
Constructor
Default Delimiters
go
scaffold.New()
{{ / }}
jet
scaffold.NewJet()
[[ / ]]
Result Categorization:
Scaffold Action
Metadata List
Description
FileActionEqual
Stable
File content unchanged
FileActionAdd
Changed
New file created
FileActionUpdate
Changed
Existing file modified
FileActionRemove
Purged
File removed from target
File paths in the metadata lists are absolute paths, constructed by joining the target directory with the relative path from the scaffold result.
Purge Behavior:
When purge is enabled and a file has FileActionRemove, the provider deletes the file from disk during Scaffold(). In noop mode, the removal is logged but not performed. When purge is disabled, purged files are only tracked in metadata and not removed.
Status
Process:
Perform a dry-run render (noop mode) to determine what the scaffold would do
When ensure is absent, filter Changed and Stable lists to only include files that actually exist on disk
The noop render reports what would happen if the scaffold were applied. For ensure: present, this is the desired output — it shows what needs to change. For ensure: absent, the raw render output is misleading after removal (it would show files to be added), so the lists are filtered to reflect what managed files actually remain on disk.
State Detection:
Target Directory
Ensure Value
Metadata
Exists
present
Changed, stable, and purged file lists from render
Exists
absent
Changed and stable filtered to files on disk, purged from render
Does not exist
Any
Empty metadata, TargetExists: false
Remove
Process:
Collect managed files from the state’s Changed and Stable lists (purged files are not removed as they don’t belong to the scaffold)
Stop when no more empty directories can be removed
Best-effort removal of the target directory (only succeeds if empty)
File Removal Order:
Files are collected from two metadata lists:
Changed - Files that were added or modified
Stable - Files that were unchanged
Purged files are not removed because they are unrelated to the scaffold and may belong to other processes.
Directory Cleanup:
For each removed file:
Track its parent directory
Repeat:
For each tracked directory:
Skip if it is the target directory itself
Skip if not empty
Remove the directory
Track its parent directory
Until no more directories removed
Best-effort: remove the target directory (fails silently if not empty)
The target directory is removed if empty after all managed files and subdirectories are cleaned up. If unrelated files remain (purged files), the directory is preserved.
Error Handling:
Condition
Behavior
Non-absolute file path
Return error immediately
File removal fails
Log error, continue with remaining files
Directory removal fails
Log error, continue with remaining directories
File does not exist
Silently skip (os.IsNotExist check)
Target directory removal fails
Log at debug level, no error returned
Template Environment
Templates receive the full templates.Env environment, which provides access to:
facts - System facts for the managed node
data - Hiera-resolved configuration data
Template helper functions
This allows templates to generate host-specific configurations based on facts and hierarchical data.
Logging
The provider wraps the CCM logger in a scaffold-compatible interface:
This adapter translates the scaffold library’s Debugf/Infof calls to CCM’s structured logging.
Platform Support
The Choria provider is platform-independent. It uses the choria-io/scaffold library for template rendering, which operates on standard filesystem operations. No platform-specific system calls are used.