Resources

Resources describe the desired state of your infrastructure. Each resource represents something to manage and is backed by a provider that implements platform-specific management logic.

Every resource has a type, a unique name, and resource-specific properties.

Resource types

  • Apply: Compose manifests from smaller reusable manifests
  • Archive: Download, extract and copy files from tar.gz and zip archives
  • Exec: Execute commands idempotently
  • File: Manage files content, ownership and more
  • Package: Install, upgrade, downgrade and remove packages using OS native packagers
  • Scaffold: Render template directories into target directories with synchronization and purge support
  • Service: Enable, disable, start, stop and restart services using OS native service managers

Common properties

All resources support the following common properties:

PropertyDescription
nameUnique identifier for the resource
ensureDesired state (values vary by resource type)
aliasAlternative name for use in subscribe, require, and logging
providerForce a specific provider
requireList of resources (type#name or type#alias) that must succeed first
health_checksHealth checks to run after applying (see Monitoring)
controlConditional execution rules (see below)

Conditional resource execution

Resources can be conditionally executed using a control section and expressions that should resolve to boolean values.

package:
  name: zsh
  ensure: 5.9
  control:
    if: lookup("facts.host.info.os") == "linux"
    unless: lookup("facts.host.info.virtualizationSystem") == "docker"
ccm ensure package zsh 5.9 \
    --if "lookup('facts.host.info.os') == 'linux'"
    --unless "lookup('facts.host.info.virtualizationSystem') == 'docker'"
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "package",
  "properties": {
    "name": "zsh",
    "ensure": "5.9",
    "control": {
      "if": "lookup(\"facts.host.info.os\") == \"linux\"",
      "unless": "lookup(\"facts.host.info.virtualizationSystem\") == \"docker\""
    }
  }
}

This installs zsh on all linux machines unless they are running inside a docker container.

The following table shows how the two conditions interact:

ifunlessResource Managed?
(not set)(not set)Yes
true(not set)Yes
false(not set)No
(not set)trueNo
(not set)falseYes
truetrueNo
truefalseYes
falsetrueNo
falsefalseNo

Subsections of Resources

Apply

The apply resource resolves and executes a child manifest within the parent manifest’s execution context. Child manifests share the parent’s session, enabling resource ordering and subscribe relationships across manifest boundaries.

Note

The apply resource is manifest-only. It has no CLI or API equivalent. Only local file paths are supported; URL-based manifest sources may be added in future.

- apply:
    - networking/manifest.yaml: {}

    - monitoring/manifest.yaml:
        data:
          alert_email: ops@example.com

This executes two child manifests in order. The second receives additional data that its templates can reference.

Ensure values

ValueDescription
presentResolve and execute the child manifest

Only present is valid. The ensure property defaults to present if not specified.

Properties

PropertyDescription
nameFile path to the child manifest (relative to parent manifest directory)
noopExecute child in noop mode (can only strengthen, never weaken)
health_check_onlyExecute child in health check mode (can only strengthen, never weaken)
allow_applyAllow the child manifest to contain its own apply resources (default: true)
dataData map passed to the child manifest, merged with resolved data

Path resolution

The name property specifies a file path relative to the directory containing the parent manifest. Absolute paths are used as-is.

# Given /opt/ccm/manifest.yaml contains:
- apply:
    - sub/manifest.yaml: {}
    # resolves to /opt/ccm/sub/manifest.yaml

For nested apply resources, each level resolves paths relative to its own manifest’s directory. After a child manifest completes, the working directory reverts to the parent’s directory.

/opt/ccm/manifest.yaml
  apply: sub/manifest.yaml        -> /opt/ccm/sub/manifest.yaml
    apply: lib/manifest.yaml      -> /opt/ccm/sub/lib/manifest.yaml

Noop and health check propagation

Noop and health check modes propagate downward through apply resources. A child can enter noop or health check mode when its parent has not, but a child can never weaken a mode that the parent has active.

Parent modeResource propertyEffective child mode
noopnoop: falsenoop
noopnoop: truenoop
normalnoop: truenoop
normalnoop: falsenormal

Health check mode follows the same pattern. If either the parent or the resource enables health check mode, the child executes in health check mode.

Running ccm apply manifest.yaml --noop forces noop on all child manifests regardless of their noop property.

Passing data to child manifests

The data property provides key-value data to the child manifest. This data merges into the child’s resolved data after its own Hiera resolution completes.

- apply:
    - app/manifest.yaml:
        data:
          port: 8080
          log_level: info

Templates in the child manifest can reference these values using standard template syntax, such as {{ lookup("data.port") }}. External data from CLI --data flags persists through the merge and takes precedence.

Restricting nested apply resources

The allow_apply property controls whether a child manifest may contain its own apply resources. Setting allow_apply to false limits the trust boundary when including manifests authored by others.

- apply:
    - vendor/manifest.yaml:
        allow_apply: false

If the child manifest contains apply resources and allow_apply is false, execution fails with an error before any child resources run.

Regardless of allow_apply, nested apply resources are limited to a maximum recursion depth of 10 levels.

Subscribe behavior

Other resources can subscribe to apply resources using the apply#name format:

- apply:
    - config/manifest.yaml: {}

- exec:
    - notify:
        command: /usr/local/bin/notify.sh
        refresh_only: true
        subscribe:
          - apply#config/manifest.yaml

The exec resource runs only if the child manifest made changes.

Shared session

The child manifest executes within the parent’s session. Resource events from child manifests are recorded in the same session as the parent, and subscribe relationships work across manifest boundaries.

If any child resource fails, the apply resource reports the failure. If the enclosing manifest sets fail_on_error: true, execution of subsequent resources in that manifest stops at that point.

Archive

The archive resource downloads and extracts archives from HTTP/HTTPS URLs. It supports tar.gz, tgz, tar, and zip formats.

Note

The archive file path (name) must have the same archive type extension as the URL. For example, if the URL ends in .tar.gz, the name must also end in .tar.gz.

- archive:
    - /opt/downloads/app-v1.2.3.tar.gz:
        url: https://releases.example.com/app/v1.2.3/app-v1.2.3.tar.gz
        checksum: "a1b2c3d4e5f6..."
        extract_parent: /opt/app
        creates: /opt/app/bin/app
        owner: app
        group: app
        cleanup: true
ccm ensure archive /opt/downloads/app.tar.gz \
    --url https://releases.example.com/app.tar.gz \
    --extract-parent /opt/app \
    --creates /opt/app/bin/app \
    --owner root --group root
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "archive",
  "properties": {
    "name": "/opt/downloads/app-v1.2.3.tar.gz",
    "url": "https://releases.example.com/app/v1.2.3/app-v1.2.3.tar.gz",
    "checksum": "a1b2c3d4e5f6...",
    "extract_parent": "/opt/app",
    "creates": "/opt/app/bin/app",
    "owner": "app",
    "group": "app",
    "cleanup": true
  }
}

This downloads the archive, extracts it to /opt/app, and removes the archive file after extraction. Future runs skip the download if /opt/app/bin/app exists.

Ensure values

ValueDescription
presentThe archive must be downloaded
absentThe archive file must not exist

Properties

PropertyDescription
nameAbsolute path where the archive will be saved
urlHTTP/HTTPS URL to download the archive from
checksumExpected SHA256 checksum of the downloaded file
extract_parentDirectory to extract the archive contents into
createsFile path; if this file exists, the archive is not downloaded or extracted
cleanupRemove the archive file after successful extraction (requires extract_parent and creates)
ownerOwner of the downloaded archive file (username)
groupGroup of the downloaded archive file (group name)
usernameUsername for HTTP Basic Authentication
passwordPassword for HTTP Basic Authentication
headersAdditional HTTP headers to send with the request (map of header name to value)
providerForce a specific provider (http only)

Authentication

The archive resource supports two authentication methods:

Basic authentication

- archive:
    - /opt/downloads/private-app.tar.gz:
        url: https://private.example.com/app.tar.gz
        username: deploy
        password: "{{ lookup('data.deploy_password') }}"
        extract_parent: /opt/app
        owner: root
        group: root
ccm ensure archive /opt/downloads/private-app.tar.gz \
    --url https://private.example.com/app.tar.gz \
    --username deploy \
    --password "{{ lookup('data.deploy_password') }}"
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "archive",
  "properties": {
    "name": "/opt/downloads/private-app.tar.gz",
    "url": "https://private.example.com/app.tar.gz",
    "username": "deploy",
    "password": "secret",
    "extract_parent": "/opt/app",
    "owner": "root",
    "group": "root"
  }
}

Custom headers

- archive:
    - /opt/downloads/app.tar.gz:
        url: https://api.example.com/releases/app.tar.gz
        headers:
          Authorization: "Bearer {{ lookup('data.api_token') }}"
          X-Custom-Header: custom-value
        extract_parent: /opt/app
        owner: root
        group: root
ccm ensure archive /opt/downloads/app.tar.gz \
    --url https://api.example.com/releases/app.tar.gz \
    --headers "Authorization:{{ lookup('data.api_token') }}"
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "archive",
  "properties": {
    "name": "/opt/downloads/app.tar.gz",
    "url": "https://api.example.com/releases/app.tar.gz",
    "headers": {
      "Authorization": "Bearer mytoken",
      "X-Custom-Header": "custom-value"
    },
    "extract_parent": "/opt/app",
    "owner": "root",
    "group": "root"
  }
}

Idempotency

The archive resource is idempotent through multiple mechanisms:

  1. Checksum verification: If a checksum is provided and the existing file matches, no download occurs.
  2. Creates file: If creates is specified and that file exists, neither download nor extraction occurs.
  3. File existence: If the archive file exists with matching checksum and owner/group, no changes are made.

For best idempotency, always specify either checksum or creates (or both).

Cleanup behavior

When cleanup: true is set:

  • The archive file is deleted after successful extraction
  • The extract_parent property is required
  • The creates property is required to track extraction state across runs

Supported archive formats

ExtensionExtraction Tool
.tar.gz, .tgztar -xzf
.tartar -xf
.zipunzip
Note

The extraction tools (tar, unzip) must be available in the system PATH.

Exec

The exec resource executes commands to bring the system into the desired state. It is idempotent when used with the creates, onlyif, or unless properties, or refreshonly mode.

Warning

Specify commands with their full path, or use the path property to set the search path.

- exec:
    - /usr/bin/touch /tmp/hello:
        creates: /tmp/hello
        timeout: 30s
        cwd: /tmp

Alternatively for long commands or to improve UX for referencing execs in require or subscribe:

- exec:
    - touch_hello:
        command: /usr/bin/touch /tmp/hello
        creates: /tmp/hello
        timeout: 30s
        cwd: /tmp
ccm ensure exec "/usr/bin/touch /tmp/hello" --creates /tmp/hello --timeout 30s
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "exec",
  "properties": {
    "name": "/usr/bin/touch /tmp/hello",
    "creates": "/tmp/hello",
    "timeout": "30s",
    "cwd": "/tmp"
  }
}

The command runs only if /tmp/hello does not exist.

Providers

The exec resource supports two providers:

ProviderDescription
posixDefault. Executes commands directly without a shell. Arguments are parsed and passed to the executable.
shellExecutes commands via /bin/sh -c "...". Use this for shell features like pipes, redirections, and builtins.

The posix provider is the default and is suitable for most commands. Use the shell provider when you need shell features:

- exec:
    - cleanup-logs:
        command: find /var/log -name '*.log' -mtime +30 -delete && echo "Done"
        provider: shell

    - check-service:
        command: systemctl is-active httpd || systemctl start httpd
        provider: shell

    - process-data:
        command: cat /tmp/input.txt | grep -v '^#' | sort | uniq > /tmp/output.txt
        provider: shell
Note

The shell provider passes the entire command string to /bin/sh -c, so shell quoting rules apply. The posix provider parses arguments using shell-like quoting but does not invoke a shell.

Properties

PropertyDescription
nameThe command to execute (used as the resource identifier)
commandAlternative command to run instead of name
cwdWorking directory for command execution
environment (array)Environment variables in KEY=VALUE format
pathSearch path for executables as a colon-separated list (e.g., /usr/bin:/bin)
returns (array)Exit codes indicating success (default: [0])
timeoutMaximum execution time (e.g., 30s, 5m); command is killed if exceeded
createsFile path; if this file exists, the command does not run
onlyifGuard command; the exec runs only if this command exits 0
unlessGuard command; the exec runs only if this command exits non-zero
refreshonly (boolean)Only run when notified by a subscribed resource
subscribe (array)Resources to subscribe to for refresh notifications (type#name or type#alias)
logoutput (boolean)Log the command output
providerForce a specific provider (posix or shell)

Guard commands

The onlyif and unless properties act as guard commands that control whether the exec runs. They are evaluated before execution and share the exec’s cwd, environment, and path settings. Guard commands run even in noop mode to accurately report what would happen.

When creates is also set, it takes precedence: if the creates file exists, the command is skipped regardless of guard results. Subscribe-triggered refreshes override all guards.

- exec:
    - install-app:
        command: /usr/local/bin/install-app.sh
        onlyif: test -f /tmp/app-package.tar.gz
        # Runs only if the package file exists

    - configure-firewall:
        command: /usr/sbin/iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
        unless: /usr/sbin/iptables -C INPUT -p tcp --dport 8080 -j ACCEPT
        # Runs only if the iptables rule does not already exist
# Runs only if the package file exists
ccm ensure exec /usr/local/bin/install-app.sh --exec-if "test -f /tmp/app-package.tar.gz"

# Runs only if the iptables rule does not already exist
ccm ensure exec "/usr/sbin/iptables -A INPUT -p tcp --dport 8080 -j ACCEPT" \
  --exec-unless "/usr/sbin/iptables -C INPUT -p tcp --dport 8080 -j ACCEPT"
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "exec",
  "properties": {
    "name": "install-app",
    "command": "/usr/local/bin/install-app.sh",
    "onlyif": "test -f /tmp/app-package.tar.gz"
  }
}
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "exec",
  "properties": {
    "name": "configure-firewall",
    "command": "/usr/sbin/iptables -A INPUT -p tcp --dport 8080 -j ACCEPT",
    "unless": "/usr/sbin/iptables -C INPUT -p tcp --dport 8080 -j ACCEPT"
  }
}

File

The file resource manages files and directories, including their content, ownership, and permissions.

Warning

Use absolute file paths and primary group names.

- file:
    - /etc/motd:
        ensure: present
        content: |
          Managed by CCM {{ now() }}
        owner: root
        group: root
        mode: "0644"
ccm ensure file /etc/motd --source /tmp/ccm/motd --owner root --group root --mode 0644
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "file",
  "properties": {
    "name": "/etc/motd",
    "ensure": "present",
    "content": "Managed by CCM\n",
    "owner": "root",
    "group": "root",
    "mode": "0644"
  }
}

This creates /etc/motd with the given content, parsed through the template engine, and sets ownership and permissions.

Ensure values

ValueDescription
presentThe file must exist
absentThe file must not exist
directoryThe path must be a directory

Properties

PropertyDescription
nameAbsolute path to the file
ensureDesired state (present, absent, directory)
contentFile contents, parsed through the template engine
sourceCopy contents from another local file
ownerFile owner (username)
groupFile group (group name)
modeFile permissions in octal notation (e.g., "0644"). For directories, the execute bit is added automatically to any permission triad that has read or write bits (e.g., "0644" becomes "0755")
providerForce a specific provider (posix only)

Package

The package resource manages system packages. Specify whether the package should be present, absent, at the latest version, or at a specific version.

Warning

Use real package names, not virtual names, aliases, or group names.

- package:
    - zsh:
        ensure: "5.9"
ccm ensure package zsh 5.9
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "package",
  "properties": {
    "name": "zsh",
    "ensure": "5.9"
  }
}

Ensure values

ValueDescription
presentThe package must be installed
latestThe package must be installed at latest version
absentThe package must not be installed
<version>The package must be installed at this version

Properties

PropertyDescription
namePackage name
ensureDesired state or version
providerForce a specific provider (dnf, apt)

Provider notes

APT (Debian/Ubuntu)

The APT provider preserves existing configuration files during package installation and upgrades. When a package is upgraded and the maintainer has provided a new version of a configuration file, the existing file is kept (--force-confold behavior).

Packages in a partially installed or config-files state (removed but configuration remains) are treated as absent. Reinstalling such packages will preserve the existing configuration files.

Note

The provider will not run apt update before installing a package. Use an exec resource to update the package index if necessary.

The provider runs non-interactively and suppresses prompts from apt-listbugs and apt-listchanges.

Scaffold

The scaffold resource renders files from a source template directory to a target directory. Templates have access to facts and Hiera data, enabling dynamic configuration generation from directory structures.

Warning

Target paths must be absolute and canonical (no . or .. components).

- scaffold:
    - /etc/app:
        ensure: present
        source: templates/app
        engine: jet
        purge: true
ccm ensure scaffold /etc/app templates/app --engine jet --purge
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "scaffold",
  "properties": {
    "name": "/etc/app",
    "ensure": "present",
    "source": "templates/app",
    "engine": "jet",
    "purge": true
  }
}

This renders templates from the templates/app directory into /etc/app using the Jet template engine, removing any files in the target not present in the source.

Note

This is implemented using the github.com/choria-io/scaffold Go library. Use this in other projects or use the included scaffold CLI tool.

Ensure values

ValueDescription
presentTarget directory must exist with rendered template files
absentManaged files must be removed; target directory removed if empty

Properties

PropertyDescription
nameAbsolute path to the target directory
sourceSource template directory path (relative to working directory or absolute)
engineTemplate engine: go or jet (default: jet)
skip_emptyDo not create empty files in rendered output
left_delimiterCustom left template delimiter
right_delimiterCustom right template delimiter
purgeRemove files in target not present in source
dataCustom data map that replaces Hiera data for template rendering
postPost-processing commands: glob pattern to command mapping
providerForce a specific provider (choria only)

Template engines

Two template engines are supported:

EngineLibraryDefault DelimitersDescription
goGo text/template{{ / }}Standard Go templates
jetJet templating[[ / ]]Jet template language

The engine defaults to jet if not specified. Delimiters can be customized via left_delimiter and right_delimiter.

Custom delimiters

- scaffold:
    - /etc/myservice:
        ensure: present
        source: templates/myservice
        engine: go
        left_delimiter: "<<"
        right_delimiter: ">>"
ccm ensure scaffold /etc/myservice templates/myservice \
    --engine go --left-delimiter "<<" --right-delimiter ">>"
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "scaffold",
  "properties": {
    "name": "/etc/myservice",
    "ensure": "present",
    "source": "templates/myservice",
    "engine": "go",
    "left_delimiter": "<<",
    "right_delimiter": ">>"
  }
}

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 {} in the command as a placeholder for the file’s full path; if omitted, the path is appended as the last argument.

- scaffold:
    - /opt/app:
        ensure: present
        source: templates/app
        post:
          - "*.go": "go fmt {}"
          - "*.sh": "chmod +x {}"
ccm ensure scaffold /opt/app templates/app \
    --post "*.go"="go fmt {}" --post "*.sh"="chmod +x {}"
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "scaffold",
  "properties": {
    "name": "/opt/app",
    "ensure": "present",
    "source": "templates/app",
    "post": [
      {"*.go": "go fmt {}"},
      {"*.sh": "chmod +x {}"}
    ]
  }
}

Post-processing runs immediately after each file is rendered. Files skipped due to skip_empty are not post-processed.

Purge behavior

When purge: true is set, files in the target directory that are not present in the source template directory are deleted during rendering. In noop mode, these deletions are logged but not performed.

When purge is disabled (the default), files not present in the source are tracked but not removed. They do not affect idempotency checks for ensure: present, meaning the resource is considered stable even if extra files exist in the target.

Removal behavior

When ensure: absent, only managed files (changed and stable) are removed. Files not belonging to the scaffold (purged files) are left untouched. After removing managed files and empty subdirectories, the target directory itself is removed on a best-effort basis; it is only deleted if empty. If unrelated files remain, the directory is preserved and no error is raised.

Idempotency

The scaffold resource determines idempotency by rendering templates in noop mode and comparing results against the target directory.

For ensure: present:

  • Changed files: Files that would be created or modified. Any changed files make the resource unstable.
  • Stable files: Files whose content matches the rendered output. At least one stable file must exist for the resource to be considered stable.
  • Purged files: Files in the target not present in the source. These only affect stability when purge is enabled.

For ensure: absent, the status check filters Changed and Stable lists to only include files that actually exist on disk. This means after a successful removal, the scaffold is considered absent even if the target directory still exists with unrelated files. Purged files never affect the absent stability check.

Source resolution

The source property is resolved relative to the manager’s working directory when it is a relative path. URL sources (with a scheme) are passed through unchanged. This allows manifests bundled with template directories to use relative paths.

Template environment

Templates receive the full template environment, which provides access to:

  • facts - System facts for the managed node
  • data - Hiera-resolved configuration data, or custom data when the data property is set
  • Template helper functions

Custom data

The data property allows supplying a custom data map that completely replaces the Hiera-resolved data for template rendering. This is useful when a scaffold needs data that differs from or is unrelated to the global Hiera data.

When data is set, templates see only the custom data through data — the Hiera data is not merged, it is replaced entirely. Facts remain available regardless.

String values in the data map support template expressions that are resolved before rendering:

- scaffold:
    - /etc/app:
        ensure: present
        source: templates/app
        engine: jet
        data:
          app_name: myapp
          version: "{{ Facts.version }}"
          port: 8080
          debug: false
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "scaffold",
  "properties": {
    "name": "/etc/app",
    "ensure": "present",
    "source": "templates/app",
    "engine": "jet",
    "data": {
      "app_name": "myapp",
      "version": "v1.0.0",
      "port": 8080,
      "debug": false
    }
  }
}

Non-string values (integers, booleans, lists, maps) are preserved as-is without template resolution.

Creating scaffolds

A scaffold source is a directory tree where every file is a template. The directory structure is mirrored directly into the target, so the source layout becomes the output layout.

Source directory structure

templates/app/
├── _partials/
│   └── header.conf
├── config.yaml
├── scripts/
│   └── setup.sh
└── README.md

This renders into:

/etc/app/
├── config.yaml
├── scripts/
│   └── setup.sh
└── README.md

The _partials directory is special — its contents are available to templates but are never copied to the target.

Template syntax

Every file in the source directory is processed as a template. The syntax depends on the engine selected.

Jet engine (default, [[ / ]] delimiters):

# config.yaml
hostname: [[ facts.hostname ]]
environment: [[ data.environment ]]
workers: [[ data.worker_count ]]

Go engine ({{ / }} delimiters):

# config.yaml
hostname: {{ .facts.hostname }}
environment: {{ .data.environment }}
workers: {{ .data.worker_count }}

The Jet engine is the default because its [[ / ]] delimiters avoid conflicts with configuration files that use curly braces (YAML, JSON, systemd units). Use the Go engine when you need access to Sprig functions.

Partials

Files inside a _partials directory are reusable template fragments. They are rendered on demand using the render function but are excluded from the output.

This is useful for shared headers, repeated configuration blocks, or any content used across multiple files.

Jet:

[[ render("_partials/header.conf", .) ]]

server {
    listen [[ data.port ]];
}

Go:

{{ render "_partials/header.conf" . }}

server {
    listen {{ .data.port }};
}

Built-in functions

Two functions are available in both template engines:

render evaluates another template file from the source directory and returns its output as a string. The partial is rendered using the same engine and data as the calling template.

[[ render("_partials/database.conf", .) ]]
{{ render "_partials/database.conf" . }}

write creates an additional file in the target directory from within a template. This is useful for dynamically generating files based on data — for example, creating one configuration file per service.

[[ write("extra.conf", "generated content") ]]
{{ write "extra.conf" "generated content" }}

Sprig functions

When using the Go template engine, all Sprig template functions are available. These provide string manipulation, math, date formatting, list operations, and more:

# Go engine example with Sprig functions
hostname: {{ .facts.hostname | upper }}
packages: {{ join ", " .data.packages }}
generated: {{ now | date "2006-01-02" }}

Example scaffold

A complete scaffold for an application configuration:

Source structure:

templates/myapp/
├── _partials/
│   └── logging.conf
├── myapp.conf
└── scripts/
    └── healthcheck.sh

_partials/logging.conf (Jet):

log_level = [[ data.log_level ]]
log_file = /var/log/myapp/[[ facts.hostname ]].log

myapp.conf (Jet):

[[ render("_partials/logging.conf", .) ]]

[server]
bind = 0.0.0.0
port = [[ data.port ]]
workers = [[ data.workers ]]

scripts/healthcheck.sh (Jet):

#!/bin/bash
curl -sf http://localhost:[[ data.port ]]/health || exit 1

Manifest using this scaffold:

- scaffold:
    - /etc/myapp:
        ensure: present
        source: templates/myapp
        purge: true
        post:
          - "*.sh": "chmod +x {}"

With facts {"hostname": "web01"} and data {"port": 8080, "workers": 4, "log_level": "info"}, this renders:

/etc/myapp/
├── myapp.conf
└── scripts/
    └── healthcheck.sh

Where myapp.conf contains:

log_level = info
log_file = /var/log/myapp/web01.log

[server]
bind = 0.0.0.0
port = 8080
workers = 4

Service

The service resource manages system services. Services have two independent properties: whether they are running and whether they are enabled to start at boot.

Warning

Use real service names, not virtual names or aliases.

Services can subscribe to other resources and restart when those resources change.

- service:
    - httpd:
        ensure: running
        enable: true
        subscribe:
          - package#httpd
ccm ensure service httpd running --enable --subscribe package#httpd
{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "service",
  "properties": {
    "name": "httpd",
    "ensure": "running",
    "enable": true,
    "subscribe": ["package#httpd"]
  }
}

Ensure values

ValueDescription
runningThe service must be running
stoppedThe service must be stopped

If ensure is not specified, it defaults to running.

Properties

PropertyDescription
nameService name
ensureDesired state (running or stopped; default: running)
enable (boolean)Enable the service to start at boot
subscribe (array)Resources to watch; restart the service when they change (type#name or type#alias)
providerForce a specific provider (systemd only)