Shell Usage

CCM is designed as a CLI-first tool. Each resource type has its own subcommand under ccm ensure, with required inputs as arguments and optional settings as flags.

The ccm ensure commands are idempotent, making them safe to run multiple times in shell scripts.

Use ccm --help and ccm <command> --help to explore available commands and options.

Managing a Single Resource

Managing a single resource is straightforward.

ccm ensure package zsh 5.8

This ensures the package zsh is installed at version 5.8.

To view the current state of a resource:

ccm status package zsh

Managing Multiple Resources

When managing multiple resources in a script, create a session first. The session records the outcome of each resource, enabling features like refreshing a service when a file changes.

#!/bin/bash

eval "$(ccm session new)"

ccm ensure package httpd
ccm ensure file /etc/httpd/conf/httpd.conf .... --require package#httpd
ccm ensure service httpd --subscribe file#/etc/httpd/conf/httpd.conf
ccm session report --remove

This creates a temporary directory for session state. If the file resource changes, the service restarts automatically.

If you prefer not to eval command output, use: export CCM_SESSION_STORE=$(mktemp -d)

Data in the CLI

As covered in the templates section, commands automatically read ./.env and ./.hiera files, merging that data into the session.

Use the --hiera flag or CCM_HIERA_DATA environment variable to specify a different data file.

With data loaded, you can access:

  • {{ lookup("data.my_data_key") }} for Hiera data
  • {{ lookup("env.MY_ENV_VAR") }} for environment variables
  • {{ lookup("facts.host.info.platformFamily") }} for system facts

Example using Hiera data:

ccm ensure package '{{ lookup("data.package") }}' '{{ lookup("data.version") }}'

Expressions use the Expr Language, enabling complex logic:

$ ccm ensure package '{{ lookup("facts.host.info.platformFamily") == "rhel" ? "httpd" : "apache2" }}'
WARN  package#httpd changed ensure=present runtime=14.509s provider=dnf

For complex conditional logic, we recommend using Hiera data with hierarchy overrides instead.

Applying Manifests

For non-shell script usage use YAML manifests with ccm apply:

See YAML Manifests for manifest format details.

Viewing System Facts

CCM gathers system facts that can be used in templates and conditions:

# Show all facts as JSON
$ ccm facts

# Show facts as YAML
$ ccm facts --yaml

# Query specific facts using gjson syntax
$ ccm facts host.info.platformFamily

Resolving Hiera Data

The ccm hiera command helps debug and test Hiera data resolution:

# Resolve a Hiera file with system facts
$ ccm hiera parse data.yaml -S

# Resolve with custom facts
$ ccm hiera parse data.yaml os=linux env=production

# Query a specific key from the result
$ ccm hiera parse data.yaml --query packages

Subsections of Shell Usage

JSON API

CCM provides a STDIN/STDOUT API for managing resources programmatically. This enables integration with external languages, allowing you to build DSLs in Ruby, Perl, Python, or any language that can execute processes and handle JSON or YAML.

Overview

The API uses a simple request/response pattern:

  1. Send a request to ccm ensure api pipe via STDIN
  2. Receive a response on STDOUT

Both JSON and YAML formats are supported for requests. The response format is always JSON, but can be explicitly set to YAML using --yaml.

Command

ccm ensure api pipe [--yaml] [--noop] [--facts <file>] [--data <file>]
FlagDescription
--yamlOutput response in YAML format instead of JSON
--noopDry-run mode; report what would change without making changes
--facts <file>Load additional facts from a YAML file
--data <file>Load Hiera-style data from a YAML file

Request Format

Requests must include a protocol identifier, resource type, and properties.

Note

JSON Schemas for these requests and responses are available at resource_ensure_request.json and resource_ensure_response.json.

JSON Request

{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "package",
  "properties": {
    "name": "nginx",
    "ensure": "present"
  }
}

YAML Request

protocol: io.choria.ccm.v1.resource.ensure.request
type: package
properties:
  name: nginx
  ensure: present

Request Fields

FieldRequiredDescription
protocolYesMust be io.choria.ccm.v1.resource.ensure.request
typeYesResource type example package
propertiesYesResource properties (varies by type)

Response Format

Responses include a protocol identifier and either a state object (on success) or an error message.

For the full response structure, see ResourceEnsureApiResponse in the Go documentation. The state field contains a TransactionEvent.

Successful Response

{
  "protocol": "io.choria.ccm.v1.resource.ensure.response",
  "state": {
    "protocol": "io.choria.ccm.v1.transaction.event",
    "event_id": "2abc123def456",
    "timestamp": "2026-01-28T10:30:00Z",
    "type": "package",
    "provider": "dnf",
    "name": "nginx",
    "requested_ensure": "present",
    "final_ensure": "1.24.0-1.el9",
    "duration": 1500000000,
    "changed": true,
    "failed": false
  }
}

Error Response

{
  "protocol": "io.choria.ccm.v1.resource.ensure.response",
  "error": "invalid protocol \"wrong.protocol\""
}

Examples

Install a Package

echo '{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "package",
  "properties": {
    "name": "htop",
    "ensure": "present"
  }
}' | ccm ensure api pipe

Manage a Service

cat <<EOF | ccm ensure api pipe
protocol: io.choria.ccm.v1.resource.ensure.request
type: service
properties:
  name: nginx
  ensure: running
EOF

Create a File

echo '{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "file",
  "properties": {
    "name": "/etc/motd",
    "ensure": "present",
    "content": "Welcome to this server\n",
    "owner": "root",
    "group": "root",
    "mode": "0644"
  }
}' | ccm ensure api pipe

Dry-Run Mode

echo '{
  "protocol": "io.choria.ccm.v1.resource.ensure.request",
  "type": "package",
  "properties": {
    "name": "vim",
    "ensure": "absent"
  }
}' | ccm ensure api pipe --noop

Resource Types

For detailed information about each resource type and its properties, see the Resource Documentation

CLI Plugins

CCM supports extending the CLI with custom commands using App Builder. This allows you to create organization-specific workflows that integrate with the ccm command.

Plugin Locations

CCM searches for plugins in two directories:

LocationPurpose
/etc/choria/ccm/plugins/System-wide plugins
$XDG_CONFIG_HOME/choria/ccm/plugins/User plugins (typically ~/.config/choria/ccm/plugins/)

Plugins in the user directory override system plugins with the same name.

Plugin File Format

Plugin files must be named <command>-plugin.yaml. The filename determines the command name:

deploy-plugin.yaml    → ccm deploy
backup-plugin.yaml    → ccm backup
myapp-plugin.yaml     → ccm myapp

Basic Plugin Structure

Plugins use App Builder’s YAML definition format. Here’s a minimal example:

# deploy-plugin.yaml
name: deploy
description: Deploy application configuration
commands:
  - name: web
    description: Deploy web server configuration
    type: exec
    command: |
      ccm apply /etc/ccm/manifests/webserver.yaml

This creates ccm deploy web which applies a manifest.

Passing Data to Manifests

Plugins can pass data to manifests through environment variables or facts. Both are accessible in manifest templates.

Using Environment Variables

Environment variables set in the plugin are available in manifests via {{ lookup("env.VAR_NAME") }}:

# deploy-plugin.yaml
name: deploy
description: Deploy with environment
commands:
  - name: production
    description: Deploy to production
    type: exec
    environment:
      - "DEPLOY_ENV=production"
      - "LOG_LEVEL=warn"
    command: |
      ccm apply /etc/ccm/manifests/app.yaml

In the manifest:

resources:
  - type: file
    name: /etc/app/config.yaml
    content: |
      environment: {{ lookup("env.DEPLOY_ENV") }}
      log_level: {{ lookup("env.LOG_LEVEL") }}

Using Facts

Pass additional facts using the --fact flag. Facts are available via {{ lookup("facts.KEY") }}:

# deploy-plugin.yaml
name: deploy
description: Deploy with custom facts
commands:
  - name: app
    description: Deploy application
    type: exec
    arguments:
      - name: version
        description: Application version to deploy
        required: true
    command: |
      ccm apply /etc/ccm/manifests/app.yaml --fact app_version={{ .Arguments.version }}

In the manifest:

resources:
  - type: package
    name: myapp
    ensure: present
    version: "{{ lookup('facts.app_version') }}"

Further Reading

For complete App Builder documentation including all command types, templating features, and advanced options, see the App Builder documentation.

Since version 0.13.0 of App Builder it has a transform and a command that can invoke CCM Manifests, this combines well with flags, arguments and form wizards to create custom UI’s that manage your infrastructure.