diff --git a/content/policy-builder/_index.markdown b/content/policy-builder/_index.markdown new file mode 100644 index 000000000..45c2534bc --- /dev/null +++ b/content/policy-builder/_index.markdown @@ -0,0 +1,59 @@ +--- +layout: default +title: Policy Builder +sorting: 45 +--- + +Policy Builder is a desktop application for writing CFEngine policy without writing the policy language by hand. +You describe the state you want your hosts in, such as a package installed, a file with certain content or a service running, by placing blocks on a canvas and connecting them. +Policy Builder turns them into CFEngine policy, checks it, tests it on containers on your own computer and deploys it to your CFEngine hub. + +## How Policy Builder works + +You build policy from ready-made pieces, each one thing a host should have or do: install a package, write a file, keep a service running. +You fill in the details, such as which package or what the file contains, put the pieces in the order they should happen, and say which hosts they're for. +Policy Builder writes the CFEngine policy for you, and you can read every line of it, which is also a good way to learn the policy language. + +From there, it follows the policy all the way to your hosts: + +1. **Build**: assemble the policy in a project, a folder on your computer. +2. **Test**: run it on containers on your own computer, and see what it changed. +3. **Deploy**: send it to your CFEngine hub, where every host fetches it. + +[The demo project][] is a complete project to explore, ready to test. See [Promises][] for the CFEngine idea behind each piece. + +## The window + +![The parts of the Policy Builder window](policy-builder-window-parts.png) + +1. **Top bar**: the project's name and settings, and its four views: **Canvas**, where you build; **Generated Policy**, the policy the blocks become; **Tests & Logs**, test runs on containers; **Deployment**, getting the policy to your hub. On the right, **AI agent** and **Save**. +2. **Activity bar**: switches the side bar between **Blocks and files** and **Modules**; **Project settings** at the bottom. +3. **Blocks**: every block, by category, with search. The tag on the right of each block, such as `files`, is the CFEngine promise type it generates. +4. **Configurations**: the project's policy files. Click one to open it on the canvas. +5. **Canvas**: the open configuration's blocks and arrows, numbered in run order, with the zoom, Tidy up and full screen tools at the bottom. +6. **Properties**: the selected block's settings, or the open configuration's when nothing is selected. +7. **Status bar**: what Policy Builder is doing, the project, the open file and its namespace. + +Keyboard shortcuts in these pages are written for macOS, with ⌘ (Command), ⇧ (Shift) and ⌥ (Option); on Windows and Linux, use Ctrl for ⌘ and Alt for ⌥. +**Help**, then **Keyboard Shortcuts** lists them all. + +## What you need + +- **Policy Builder** on your computer: see [Installing Policy Builder][]. +- **A CFEngine hub, with hosts bootstrapped to it**, to deploy to: the hub is the policy server your hosts fetch their policy from, and a host joins it by bootstrapping to it when CFEngine is installed. Community and Enterprise hubs both work; see [Installing CFEngine][]. You can build and test policy without one. +- **CFEngine 3.27 or later** on the hub and hosts: the generated policy relies on promises running in the order they're written, which is new in 3.27. +- **CFEngine Enterprise** for Mission Portal and for deploying through a git repository the hub pulls. A Community hub is deployed to over SSH instead. + +## In this section + +1. [Installing Policy Builder][] +2. [Policy Builder projects][]: what a project is, creating, opening and saving one +3. [Building policy on the canvas][]: blocks, arrows, conditions, groups and configurations +4. [Generated policy][]: what Policy Builder writes, and how to read it +5. [Values and templates][]: variables, classes, lists and data, and templates +6. [Modules in Policy Builder][]: CFEngine Build modules +7. [Test environments][]: running the policy on containers before it reaches real hosts +8. [Deploying from Policy Builder][]: to a hub, through git or over SSH +9. [AI agents in Policy Builder][]: letting an AI agent build with you +10. [The demo project][]: a complete project to explore, using everything above +11. [Block reference][]: every block, its settings and the policy it generates, and [Custom CFEngine and Call Method][] for what no block does diff --git a/content/policy-builder/ai-agents.markdown b/content/policy-builder/ai-agents.markdown new file mode 100644 index 000000000..8708f0905 --- /dev/null +++ b/content/policy-builder/ai-agents.markdown @@ -0,0 +1,67 @@ +--- +layout: default +title: AI agents in Policy Builder +sorting: 100 +--- + +AI agents such as Claude Code can build and change a project in Policy Builder while you watch. +Policy Builder runs an MCP server, the standard way AI agents use the tools of other applications: through it, an agent adds blocks, connects them, sets variables and conditions, adds modules and runs tests, with every change showing on the canvas as it's made. +The agent uses its own account and model; Policy Builder doesn't send your project anywhere itself. + +## Connecting an agent + +1. Click **AI agent** in the top bar, or choose **File**, then **Connect AI Agent…**. +2. Turn on **Let AI agents connect**. +3. Pick your **Agent**: Claude Code, Gemini CLI, OpenAI Codex, VS Code (Copilot), Cursor, or another MCP client. +4. Copy what Policy Builder shows and use it as it says: a command to run once in a terminal, or settings to add to the agent's configuration file. + +![The Connect an AI agent dialog](policy-builder-connect-agent-dialog.png) + +For Claude Code, the command looks like this: + +```command +claude mcp add --transport http --scope user cfengine-policy-builder http://127.0.0.1:/mcp --header "Authorization: Bearer " +``` + +Only programs on your computer that know the token can connect. +Keep Policy Builder open, with the project the agent should work on: the agent works on the open project, and nothing is saved until you or the agent save it. + +Then ask the agent in its own window, for example: "In Policy Builder, add a block that installs chrony and keep the chronyd service running after it." + +## What an agent can do + +An agent can always read the project: its files, blocks, generated policy, variables and test results. +What it may change is set in the same dialog, under **What agents may do**, for every agent at once: + +| Permission | Lets an agent | +| ---------------------------------- | ------------------------------------------------------------------------------- | +| **Edit blocks and variables** | Add and change blocks, arrows, groups, entries, transformers and conditions. | +| **Files and folders** | Add, rename and describe policy files and folders. | +| **Create, open and save projects** | Write the project to disk. | +| **Run tests** | Set up test environments and run them in Docker. | +| **Allow deleting** | Remove blocks, arrows, entries, transformer steps, files and test environments. | + +What's turned off isn't offered to agents at all. +(_Transformers_ are a variable's transforms, described in [Values and templates][].) +Agents also get a guide to working in Policy Builder, so they use the blocks, check their work and ask you before judgement calls such as a schedule or a looser permission. + +## While an agent works + +When an agent starts working, a bar at the top of the window says what it's doing, and the editor is locked so the two of you don't edit at the same time. +**Log** opens a panel at the bottom that lists each step the agent took, and how long it took. + +![An AI agent at work, with its log open](policy-builder-agent-working.png) + +**Stop** stops the agent at once: its next call is refused, and it's told you stopped it. You can add a **Note for the agent**, such as "wrong project", to tell it why. +**Allow again** lets it continue. + +## Reviewing an agent's changes + +Each change an agent makes is one undo step, so ⌘Z takes back its changes one at a time. +Before saving, read what it built: + +- the canvas, and each block's settings in Properties; +- the **Generated Policy** view, for the exact policy; +- **Tests & Logs**, if it ran tests. + +The guide asks agents to record their decisions in the note of the block they affect, so check the notes too. diff --git a/content/policy-builder/blocks/_index.markdown b/content/policy-builder/blocks/_index.markdown new file mode 100644 index 000000000..4f66e8b44 --- /dev/null +++ b/content/policy-builder/blocks/_index.markdown @@ -0,0 +1,24 @@ +--- +layout: default +title: Block reference +sorting: 110 +# Generated by scripts/docs-blocks.mjs in cfengine/policy-builder, from its block descriptors: edit those, not this page. +--- + +Every block Policy Builder offers, by category: what it does, its settings, and the CFEngine policy it generates. + +| Page | Blocks | +| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [File blocks][] | [Clean Up Old Files][File blocks#Clean Up Old Files], [Copy Directory][File blocks#Copy Directory], [Copy File][File blocks#Copy File], [Create Directory][File blocks#Create Directory], [Create Symlink][File blocks#Create Symlink], [Create/Edit File][File blocks#Create/Edit File], [Remove Directory][File blocks#Remove Directory], [Remove File][File blocks#Remove File], [Render Template][File blocks#Render Template], [Rotate Log][File blocks#Rotate Log], [Set Permissions][File blocks#Set Permissions], [Watch File for Changes][File blocks#Watch File for Changes] | +| [File content blocks][] | [Comment Out Lines][File content blocks#Comment Out Lines], [Ensure Lines in File][File content blocks#Ensure Lines in File], [Remove Matching Lines][File content blocks#Remove Matching Lines], [Replace Text in File][File content blocks#Replace Text in File], [Set Config Values][File content blocks#Set Config Values] | +| [Variable and class blocks][] | [Define Class][Variable and class blocks#Define Class], [Define Variable][Variable and class blocks#Define Variable] | +| [Package blocks][] | [Install Package][Package blocks#Install Package], [Remove Package][Package blocks#Remove Package] | +| [Command blocks][] | [Cron Job][Command blocks#Cron Job], [Run Command][Command blocks#Run Command] | +| [Service blocks][] | [Manage Service][Service blocks#Manage Service] | +| [User blocks][] | [Manage Users][User blocks#Manage Users] | +| [Storage blocks][] | [Mount Storage][Storage blocks#Mount Storage] | +| [Report blocks][] | [Report Message][Report blocks#Report Message] | +| [Process blocks][] | [Signal Process][Process blocks#Signal Process] | +| [Blocks from modules][] | [Git Checkout][Blocks from modules#Git Checkout], [HTTP Request][Blocks from modules#HTTP Request], [Run Ansible Playbook][Blocks from modules#Run Ansible Playbook], [Systemd Unit][Blocks from modules#Systemd Unit] | +| [Method blocks][] | [Call Method][Method blocks#Call Method], [Custom CFEngine][Method blocks#Custom CFEngine] | +| [Custom CFEngine and Call Method][] | When no block does what you need, policy you write yourself or a bundle that already exists | diff --git a/content/policy-builder/blocks/blocks-from-modules.markdown b/content/policy-builder/blocks/blocks-from-modules.markdown new file mode 100644 index 000000000..c876e10b1 --- /dev/null +++ b/content/policy-builder/blocks/blocks-from-modules.markdown @@ -0,0 +1,143 @@ +--- +layout: default +title: Blocks from modules +sorting: 110 +# Generated by scripts/docs-blocks.mjs in cfengine/policy-builder, from its block descriptors: edit those, not this page. +--- + +Blocks whose promise types come from CFEngine Build modules. Adding one adds its module to the project; saving installs it with `cfbs`. The module runs with Python, so hosts need `python3`. + +## Git Checkout + +Clones a git repository into a folder and keeps it at a branch, tag or commit. + +**Generates:** a `git` promise. + +**Module:** `promise-type-git`, added to the project with the block. + +### Settings + +| Setting | Required | Description | +| ------------------------- | -------- | --------------------------------------------------------------------------------------------------------- | +| **Folder** | Yes | Where the repository is cloned, e.g. /srv/app. Needs git on the host. | +| **Repository URL** | Yes | E.g. https://github.com/cfengine/starter_pack. A private one needs credentials for root (gitcredentials). | +| **Branch, tag or commit** | Yes | E.g. main or master: the git module can't follow the remote's default branch unnamed. | +| **Keep it updated** | Yes | One of: **Yes: pull new commits**, **No: clone once**. Default: **Yes: pull new commits**. | +| **Local changes** | Yes | One of: **Keep them**, **Discard them**. Default: **Keep them**. | +| **History depth** | No | Empty: the full history. 1: the latest commit only. At least 1. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Clone the app +git: + "/srv/app" + repository => "https://github.com/cfengine/starter_pack", + version => "main", + update => "true", + force => "false"; +``` + +## HTTP Request + +Sends an HTTP(S) request, e.g. to download a file or call an API. + +**Generates:** an `http` promise. + +**Module:** `promise-type-http`, added to the project with the block. + +### Settings + +| Setting | Required | Description | +| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| **URL** | Yes | Starting with https:// or http://. | +| **Method** | Yes | One of: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`. Default: `GET`. | +| **Save the response to** | No | Empty: the response is dropped (an OK status keeps the promise). The request runs on every agent run: set How often, or a condition. | +| **Headers** | No | One per line, Name: value. | +| **Body** | No | Sent as is; @/some/path sends a file. | +| **Server certificate** | Yes | One of: **Checked**, **Not checked (insecure)**. Default: **Checked**. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Download the logo +http: + "https://cfengine.com/images/cfengine-logo.svg" + method => "GET", + file => "/var/www/html/logo.svg", + insecure => "false"; +``` + +## Run Ansible Playbook + +Runs an Ansible playbook on the host (connection local by default). + +**Generates:** an `ansible` promise. + +**Module:** `promise-type-ansible`, added to the project with the block. + +### Settings + +| Setting | Required | Description | +| ------------------ | -------- | --------------------------------------------------------------------------------------------- | +| **Playbook** | Yes | Its absolute path on the host, e.g. /srv/ansible/site.yml. Needs Ansible on the host. | +| **Inventory** | No | The inventory file or host list, e.g. /srv/ansible/hosts. Empty: Ansible's default inventory. | +| **Tags** | No | Only the plays and tasks with these tags, one per line. | +| **Limit to hosts** | No | Hosts or groups, one per line. | +| **Become** | Yes | One of: **No**, **Yes (sudo)**. Default: **No**. | +| **Become user** | No | Empty: root. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Run the site playbook +ansible: + "/srv/ansible/site.yml" + tags => { "web" }, + become => "false"; +``` + +## Systemd Unit + +Keeps a systemd service running or stopped, and can write its unit file. + +**Generates:** a `systemd` promise. + +**Module:** `promise-type-systemd`, added to the project with the block. + +### Settings + +| Setting | Required | Description | +| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Service name** | Yes | E.g. myapp (myapp.service). | +| **State** | Yes | One of: **Running**, **Stopped**, **Removed (with its unit file)**, **Restarted on every run**, **Reloaded on every run**. Default: **Running**. | +| **Start on boot** | Yes | One of: **Yes**, **No**. Default: **Yes**. | +| **Reload systemd first** | Yes | daemon-reload before acting, e.g. after a unit file changed outside this block. One of: **Yes**, **No**. Default: **No**. | +| **Description** | No | Setting any unit field below writes the unit file (/etc/systemd/system/.service). | +| **Start commands** | No | ExecStart, one per line, e.g. /usr/bin/myapp --port 8080. | +| **Type** | No | One of: **Default (simple)**, **simple**, **forking**, **oneshot**, **notify**. | +| **Run as user** | No | The user the service runs as. Empty: root. | +| **Restart** | No | One of: **Never**, **On failure**, **Always**. | +| **Start after** | No | Units, one per line, e.g. network-online.target. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Run my app +systemd: + "myapp" + state => "started", + enabled => "true", + daemon_reload => "false", + unit_description => "My app", + service_exec_start => { "/usr/bin/myapp --port 8080" }, + unit_after => { "network-online.target" }; +``` diff --git a/content/policy-builder/blocks/command-blocks.markdown b/content/policy-builder/blocks/command-blocks.markdown new file mode 100644 index 000000000..9fd41b3d1 --- /dev/null +++ b/content/policy-builder/blocks/command-blocks.markdown @@ -0,0 +1,61 @@ +--- +layout: default +title: Command blocks +sorting: 50 +# Generated by scripts/docs-blocks.mjs in cfengine/policy-builder, from its block descriptors: edit those, not this page. +--- + +Blocks that run commands, now or on a schedule. + +## Cron Job + +Adds a line to a user's crontab, and reloads cron when it changes. + +**Generates:** a `methods` promise. + +### Settings + +| Setting | Required | Description | +| ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Command** | Yes | Start with an absolute path. Runs with cron's environment, not the agent's. The job runs every day of the month and week, at the hours and minutes set below; Windows hosts skip it. | +| **Run as** | Yes | Default: `root`. | +| **Hours** | Yes | Cron hour field: * for every hour, 3 or 0,12 or */6. Default: `*`. | +| **Minutes** | Yes | Cron minute field: 0, 15,45 or */10. Default: `0`. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Back up nightly +methods: + "cron job for root" + usebundle => default:cronjob( + "/usr/local/bin/backup.sh", "root", "3", "30" + ); +``` + +## Run Command + +Runs a command via the shell. + +**Generates:** a `commands` promise. + +### Settings + +| Setting | Required | Description | +| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Command** | Yes | Start with an absolute path (/usr/bin/…). $(name) is a CFEngine variable, so write a shell $( ) as $(const.dollar)( ). | +| **How often** | No | A command runs every time the agent does (every 5 minutes by default) unless limited here. One of: **Every agent run**, **At most once an hour**, **At most once a day**, **At most once a week**, or **Other…** and **Minutes between runs**, at least 1. Default: **At most once a day**. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Update CA certificates +commands: + "/usr/sbin/update-ca-certificates" + contain => default:in_shell, + action => default:if_elapsed("1440"); +``` diff --git a/content/policy-builder/blocks/custom-cfengine.markdown b/content/policy-builder/blocks/custom-cfengine.markdown new file mode 100644 index 000000000..e2f84c62e --- /dev/null +++ b/content/policy-builder/blocks/custom-cfengine.markdown @@ -0,0 +1,67 @@ +--- +layout: default +title: Custom CFEngine and Call Method +sorting: 130 +description: When no block does what you need, policy you write yourself or a bundle that already exists +--- + +This page is for when you already know some of the CFEngine policy language. Blocks cover the most common promises, but not everything CFEngine can do. +When no block does what you need, a **Custom CFEngine** block holds policy you write yourself, and a **Call Method** block runs a bundle that already exists elsewhere. +Both still sit on the canvas like any block: they have a label, a note, a condition, arrows and How often. + +Keep them for the part no block covers: what stays in other blocks remains easy to read and edit for everyone working on the project. + +## Custom CFEngine + +A Custom CFEngine block has two fields: + +- **Promises**: promise sections as you'd write them in a `bundle agent`, such as `files:` followed by its promises. +- **Bodies and bundles it uses**: any body or bundle the promises call that isn't in the _standard library_. The standard library is the collection of common bodies and bundles that comes with masterfiles (`lib/`), such as `mog()` for permissions or `recurse()` for directory depth; those need no definition here. + +For example, to copy a directory and remove files the source doesn't have, which no block offers: + +```cf3 {skip} +files: + "/var/www/site" + copy_from => mirror("/srv/site"), + depth_search => default:recurse("inf"); +``` + +with this in **Bodies and bundles it uses**: + +```cf3 +body copy_from mirror(from) +{ + source => "$(from)"; + purge => "true"; +} +``` + +The promises become a bundle of their own, which the configuration's `main` bundle calls in its place on the canvas: + +```cf3 {skip} +methods: + "Mirror the site" usebundle => mirror_the_site; +``` + +A few rules follow from the block living in its configuration's namespace: + +- The standard library's bodies and bundles need `default:` in front, such as `default:recurse("inf")` or `default:mog("644", "root", "root")`. +- Bodies and bundles from **Bodies and bundles it uses** are written without a namespace. +- `$(vars.name)` reads a variable of the same configuration; another configuration's needs its namespace, such as `$(common:vars.name)`. + +Policy Builder checks the text as you type, with the CFEngine linter: problems are shown under the field, with their line. +If the text doesn't parse, the block is left out of the generated policy until it's fixed, and Properties says why. + +**Tip:** say in the block's note what it does and why it's written by hand, so the next person reading the canvas knows. + +## Call Method + +**Call Method** runs a bundle that already exists: one from CFEngine's standard library, from a CFEngine Build module, or one written by hand outside the project. + +- **Bundle name**: the bundle to call, such as `my_bundle`, looked up in the `default` namespace, or `ns:my_bundle` for another namespace. +- **Arguments**: one per line, in the bundle's parameter order. `$(…)` passes a variable's value, and `@(…)` a list. + +Blocks on the canvas don't need Call Method to call each other: arrows already order them. + +See [Bundles][] and [Bodies][] for how CFEngine defines and calls them. diff --git a/content/policy-builder/blocks/file-blocks.markdown b/content/policy-builder/blocks/file-blocks.markdown new file mode 100644 index 000000000..25ff4ca14 --- /dev/null +++ b/content/policy-builder/blocks/file-blocks.markdown @@ -0,0 +1,347 @@ +--- +layout: default +title: File blocks +sorting: 10 +# Generated by scripts/docs-blocks.mjs in cfengine/policy-builder, from its block descriptors: edit those, not this page. +--- + +Blocks that create, copy, change and remove files and directories, and set their permissions. + +## Clean Up Old Files + +Deletes files under a directory that haven't changed for a number of days. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| --------------------- | -------- | -------------------------------------------------------------------------------------------------- | +| **Directory** | Yes | Its contents are cleaned, not the directory itself, e.g. /var/tmp. | +| **Older than (days)** | Yes | At least 1. Default: `7`. | +| **Recursion depth** | Yes | "inf" for the whole tree, or a number like "1" for just the directory's own files. Default: `inf`. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Clean up old temp files +files: + "/var/tmp" + delete => default:tidy, + file_select => default:days_old("14"), + depth_search => default:recurse("inf"); +``` + +## Copy Directory + +Recursively copies a directory tree and enforces permissions on every file. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| ------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ | +| **Destination directory** | Yes | Where the copy goes, e.g. /opt/app/static. | +| **Source directory** | Yes | The directory to copy, on the host itself, e.g. /srv/build/static. | +| **Recursion depth** | Yes | How many levels deep to copy — "inf" for the whole tree, or a number like "1" for just its immediate contents. Default: `inf`. | +| **Permissions** | Yes | Applied to every file; directories also keep their execute bits, so they stay enterable. Default: `644`. | +| **Owner** | Yes | Default: `root`. | +| **Group** | Yes | Default: `root`. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Copy the static files +files: + "/opt/app/static/." + create => "true", + copy_from => default:local_cp("/srv/build/static"), + depth_search => default:recurse("inf"), + perms => mog_dirs("644", "root", "root"); + +body perms mog_dirs(mode, user, group) +{ + owners => { "$(user)" }; + groups => { "$(group)" }; + mode => "$(mode)"; + rxdirs => "true"; +} +``` + +## Copy File + +Copies a file from a source path known to the agent, enforcing permissions. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Destination path** | Yes | Where the file is copied to, e.g. /etc/motd. | +| **Source path** | Yes | An absolute path on this host, or on the server when one is given. | +| **From server** | No | Copy from this host instead, e.g. $(sys.policy_hub), comparing content. Its cf-serverd must allow access to the source path (the hub's default policy shares /var/cfengine/masterfiles). | +| **Permissions** | Yes | Default: `644`. | +| **Owner** | Yes | Default: `root`. | +| **Group** | Yes | Default: `root`. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Copy the message of the day +files: + "/etc/motd" + copy_from => default:local_cp("/srv/files/motd"), + perms => default:mog("644", "root", "root"); +``` + +## Create Directory + +Ensures directories exist with the given ownership and mode, parents included. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| --------------- | -------- | ---------------------------------------------------------------------------------- | +| **Directory** | Yes | One absolute path per line. Missing parent directories are created too (mode 700). | +| **Permissions** | Yes | Default: `755`. | +| **Owner** | Yes | Default: `root`. | +| **Group** | Yes | Default: `root`. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Create the web root +files: + "/var/www/html/." + create => "true", + perms => default:mog("755", "www-data", "www-data"); +``` + +## Create Symlink + +Ensures a symbolic link points at a target. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| ------------------------------ | -------- | ----------------------------------------------------------------------------------- | +| **Link** | Yes | Where the link goes, e.g. /etc/nginx/sites-enabled/app. | +| **Points to** | Yes | Created even if the target doesn't exist yet. | +| **If something else is there** | Yes | One of: **Leave it, and fail**, **Move it aside**. Default: **Leave it, and fail**. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Enable the app site +files: + "/etc/nginx/sites-enabled/app" + link_from => default:ln_s("/etc/nginx/sites-available/app"), + move_obstructions => "false"; +``` + +## Create/Edit File + +Ensures a file exists with exactly the given content and permissions. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| --------------- | -------- | --------------------------------------------------------------------------------------------- | +| **File path** | Yes | The file to create, or keep with this content, e.g. /etc/app.conf. | +| **Content** | Yes | The whole file: anything else in it is replaced. $(name) references are expanded by CFEngine. | +| **Permissions** | Yes | Default: `644`. | +| **Owner** | Yes | Default: `root`. | +| **Group** | Yes | Default: `root`. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Write the app config +files: + "/etc/app.conf" + create => "true", + content => "name = \"app\" +port = 8080 +", + perms => default:mog("644", "root", "root"); +``` + +## Remove Directory + +Deletes directories and everything in them. + +**Generates:** a `methods` promise. + +### Settings + +| Setting | Required | Description | +| ------------- | -------- | ----------------------------------------------------------------------------------------- | +| **Directory** | Yes | One absolute path per line; the whole tree goes. A plain file at the path is removed too. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Remove the build cache +methods: + "remove /tmp/build" usebundle => default:rm_rf("/tmp/build"); +``` + +## Remove File + +Ensures one or more files are absent. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| ------------- | -------- | -------------------------------------------------------------- | +| **File path** | Yes | One absolute path per line. Taken literally, not as a pattern. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Remove the default site +files: + "/etc/nginx/sites-enabled/default" + delete => default:tidy, + pathtype => "literal"; +``` + +## Render Template + +Renders a Mustache template to a destination file. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Destination path** | Yes | The file rendered on the managed host, e.g. /etc/nginx/nginx.conf. | +| **Template content** | Yes | Mustache: {{{vars.:vars.}}} inserts a variable (triple braces: not HTML-escaped), {{#classes.:}}…{{/classes.:}} keeps a section only while the class is set. It's saved as its own .mustache file next to the policy, used as written: $(name) isn't expanded there, use {{{vars.…}}}. | +| **Permissions** | Yes | Default: `644`. | +| **Owner** | Yes | Default: `root`. | +| **Group** | Yes | Default: `root`. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Render the app config +files: + "/etc/app.conf" + create => "true", + edit_template => "$(this.promise_dirname)/templates/example_render_the_app_config.mustache", + template_method => "mustache", + perms => default:mog("644", "root", "root"); +``` + +## Rotate Log + +Rotates log files once they grow past a size, keeping a number of old copies. + +**Generates:** a `methods` promise. + +### Settings + +| Setting | Required | Description | +| --------------------------- | -------- | ----------------------------------------------------------------------------------------- | +| **Log file** | Yes | One absolute path per line; a missing file is skipped. | +| **Rotate when bigger than** | Yes | Bytes, or with a unit: 500k, 10M, 1G. Default: `10M`. | +| **Old copies to keep** | Yes | The file moves to .1, .2 … and the oldest is overwritten. At least 1. Default: `5`. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Rotate the app log +methods: + "rotate /var/log/app.log" + usebundle => default:logrotate("/var/log/app.log", "50M", "5"); +``` + +## Set Permissions + +Enforces ownership and mode on one or more paths, without managing content. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| ---------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- | +| **Path** | Yes | One absolute path per line. | +| **Permissions** | Yes | Directories keep their execute bits, so they stay enterable. Default: `644`. | +| **Owner** | Yes | Default: `root`. | +| **Group** | Yes | Default: `root`. | +| **Everything under each path too** | No | Applies to every file and directory below, on the same filesystem. Directories stay enterable whatever the mode. Default: No. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Lock down the shadow file +files: + "/etc/shadow" perms => mog_dirs("640", "root", "shadow"); + +body perms mog_dirs(mode, user, group) +{ + owners => { "$(user)" }; + groups => { "$(group)" }; + mode => "$(mode)"; + rxdirs => "true"; +} +``` + +## Watch File for Changes + +Reports when a file's content changes (repaired on a change). + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| ------------- | -------- | ---------------------------------------------------------------------------------------- | +| **File path** | Yes | One absolute path per line. The first run only records the hash, and counts as a change. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Watch the account files +vars: + "watch_the_account_files_path" slist => { "/etc/passwd", "/etc/group" }; + +files: + "$(watch_the_account_files_path)" changes => default:detect_content; +``` diff --git a/content/policy-builder/blocks/file-content-blocks.markdown b/content/policy-builder/blocks/file-content-blocks.markdown new file mode 100644 index 000000000..51624e74c --- /dev/null +++ b/content/policy-builder/blocks/file-content-blocks.markdown @@ -0,0 +1,138 @@ +--- +layout: default +title: File content blocks +sorting: 20 +# Generated by scripts/docs-blocks.mjs in cfengine/policy-builder, from its block descriptors: edit those, not this page. +--- + +Blocks that change what is inside a file, line by line, without replacing the whole file. + +## Comment Out Lines + +Comments out the lines of a file that match a pattern. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| ------------------ | -------- | --------------------------------------------------------------- | +| **File path** | Yes | An absolute path. | +| **Line pattern** | Yes | A regular expression the whole line must match, e.g. Listen 8.* | +| **Comment marker** | Yes | Put in front of each matching line. Default: `#`. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Comment out port 8080 +files: + "/etc/nginx/nginx.conf" + edit_line => default:comment_lines_matching("listen 8080.*", "#"); +``` + +## Ensure Lines in File + +Appends lines that are missing from a file, leaving the rest of it alone. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| -------------------------- | -------- | --------------------------------------------------------------------------------------------------------- | +| **File path** | Yes | An absolute path. | +| **Lines** | Yes | One line per line; each is appended at the end of the file unless it's already there, exactly as written. | +| **If the file is missing** | Yes | One of: **Create it**, **Leave it missing**. Default: **Create it**. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Add the app to hosts +files: + "/etc/hosts" + create => "true", + edit_line => default:append_if_no_lines("10.0.0.5 db.local"); +``` + +## Remove Matching Lines + +Deletes every line of a file that matches a pattern. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| ---------------- | -------- | ------------------------------------------------------------------------------------------------ | +| **File path** | Yes | An absolute path. | +| **Line pattern** | Yes | A regular expression the whole line must match, e.g. PermitRootLogin.* (not a substring search). | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Remove passwordless sudo +files: + "/etc/sudoers" edit_line => default:delete_lines_matching(".*NOPASSWD.*"); +``` + +## Replace Text in File + +Replaces every match of a pattern in a file, like sed. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- | +| **File path** | Yes | An absolute path. | +| **Find** | Yes | A regular expression, e.g. example\.com. The replacement mustn't match it again, or the agent reports the edit as not convergent. | +| **Replace with** | No | Empty removes the matched text. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Use the demo domain +files: + "/etc/nginx/nginx.conf" + edit_line => default:regex_replace("example\.com", "demo.local"); +``` + +## Set Config Values + +Sets keys in a config file: updates, uncomments or adds each line. + +**Generates:** a `files` promise. + +### Settings + +| Setting | Required | Description | +| --------------- | -------- | -------------------------------------------------------------------------------------------------------------- | +| **File path** | Yes | An absolute path, e.g. /etc/ssh/sshd_config or /etc/sysctl.conf. | +| **File format** | Yes | One of: **Key value**, **key=value**. Default: **Key value**. | +| **Settings** | Yes | One per line: the key, then its value after a space or =, e.g. PermitRootLogin no. Other lines are left alone. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Harden SSH +vars: + "harden_ssh_settings[PermitRootLogin]" string => "no"; + "harden_ssh_settings[MaxAuthTries]" string => "3"; + +files: + "/etc/ssh/sshd_config" + edit_line => default:set_line_based( + "security:main.harden_ssh_settings", " ", "\s+", ".*", "\s*#\s*" + ); +``` diff --git a/content/policy-builder/blocks/method-blocks.markdown b/content/policy-builder/blocks/method-blocks.markdown new file mode 100644 index 000000000..fc6ea2571 --- /dev/null +++ b/content/policy-builder/blocks/method-blocks.markdown @@ -0,0 +1,70 @@ +--- +layout: default +title: Method blocks +sorting: 120 +# Generated by scripts/docs-blocks.mjs in cfengine/policy-builder, from its block descriptors: edit those, not this page. +--- + +Blocks for what no other block does: calling an existing bundle, or writing CFEngine policy yourself. [Custom CFEngine and Call Method][] explains when and how to use them. + +## Call Method + +Calls a bundle outside this canvas: stdlib, a module, or hand-written CFEngine. + +**Generates:** a `methods` promise. + +### Settings + +| Setting | Required | Description | +| --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Bundle name** | Yes | A bundle that already exists elsewhere — CFEngine's standard library, a cfbs module, or one you wrote by hand — e.g. "my_bundle" (looked up in the default namespace) or "ns:my_bundle". Canvas blocks call each other automatically; this isn't for that. | +| **Arguments** | No | One per line, in the bundle's parameter order. $(…) passes a variable's value, @(…) a list. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Run site checks +methods: + "Run site checks" usebundle => default:site_checks; +``` + +## Custom CFEngine + +Hand-written CFEngine promises, for what no block covers. + +**Generates:** the promises and definitions you write. + +### Settings + +| Setting | Required | Description | +| ------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Promises** | Yes | Promise sections as in a bundle agent, e.g. files: then its promises. They run as a bundle of their own, in this file's namespace: write the standard library's bodies and bundles as default:name. | +| **Bodies and bundles it uses** | No | Any body or bundle the promises call that isn't in the standard library, e.g. body copy_from mirror(from) { … }. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Mirror the site +methods: + "Mirror the site" usebundle => mirror_the_site; + +# Custom CFEngine "Mirror the site", run as one step of main. +bundle agent mirror_the_site +{ + files: + "/var/www/site" + copy_from => mirror("/srv/site"), + depth_search => default:recurse("inf"); +} + +# Bodies and bundles of Custom CFEngine "Mirror the site". +body copy_from mirror(from) +{ + source => "$(from)"; + purge => "true"; +} +``` diff --git a/content/policy-builder/blocks/package-blocks.markdown b/content/policy-builder/blocks/package-blocks.markdown new file mode 100644 index 000000000..22d5d1753 --- /dev/null +++ b/content/policy-builder/blocks/package-blocks.markdown @@ -0,0 +1,55 @@ +--- +layout: default +title: Package blocks +sorting: 40 +# Generated by scripts/docs-blocks.mjs in cfengine/policy-builder, from its block descriptors: edit those, not this page. +--- + +Blocks that install and remove software packages, with the host’s package manager. + +## Install Package + +Ensures one or more packages are present. + +**Generates:** a `packages` promise. + +### Settings + +| Setting | Required | Description | +| ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Package name** | Yes | One package per line. | +| **Version** | No | Empty: any installed version will do. A version (e.g. 1.24.0-1) installs exactly that one, latest keeps it at the newest available. Applies to every package listed. | +| **Package manager** | No | The package_module body that installs the packages. A custom one: its name (in default:) or namespace:name. One of: **Platform default**, **apt_get (Debian, Ubuntu)**, **yum (Red Hat, CentOS, Amazon Linux)**, **dnf (Red Hat, Fedora)**, **dnf_group (dnf package groups)**, **zypper (SUSE)**, **apk (Alpine)**, **pkg (FreeBSD)**, **freebsd_ports (FreeBSD ports)**, **pkgsrc**, **slackpkg (Slackware)**, **nimclient (AIX)**, **snap**, or **Custom…** and **Custom body name**. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Install nginx +packages: + "nginx" policy => "present"; +``` + +## Remove Package + +Ensures one or more packages are absent. + +**Generates:** a `packages` promise. + +### Settings + +| Setting | Required | Description | +| ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Package name** | Yes | One package per line. | +| **Package manager** | No | The package_module body that removes the packages. A custom one: its name (in default:) or namespace:name. One of: **Platform default**, **apt_get (Debian, Ubuntu)**, **yum (Red Hat, CentOS, Amazon Linux)**, **dnf (Red Hat, Fedora)**, **dnf_group (dnf package groups)**, **zypper (SUSE)**, **apk (Alpine)**, **pkg (FreeBSD)**, **freebsd_ports (FreeBSD ports)**, **pkgsrc**, **slackpkg (Slackware)**, **nimclient (AIX)**, **snap**, or **Custom…** and **Custom body name**. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Remove telnet +packages: + "telnet" policy => "absent"; +``` diff --git a/content/policy-builder/blocks/process-blocks.markdown b/content/policy-builder/blocks/process-blocks.markdown new file mode 100644 index 000000000..ccaf55482 --- /dev/null +++ b/content/policy-builder/blocks/process-blocks.markdown @@ -0,0 +1,32 @@ +--- +layout: default +title: Process blocks +sorting: 100 +# Generated by scripts/docs-blocks.mjs in cfengine/policy-builder, from its block descriptors: edit those, not this page. +--- + +Blocks that act on running processes. + +## Signal Process + +Sends a signal to processes matching a pattern. + +**Generates:** a `processes` promise. + +### Settings + +| Setting | Required | Description | +| ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Process pattern** | Yes | A regular expression matched anywhere in the process's command line, not just its name — "nginx" also matches "vim nginx.conf". Anchor it (e.g. ^/usr/sbin/nginx) to be precise. | +| **Signal** | Yes | One of: `hup`, `term`, `kill`, `int`, `cont`, `stop`. Default: `hup`. | +| **How often** | No | Every matching process gets the signal each time this runs. One of: **Every agent run**, **At most once an hour**, **At most once a day**, **At most once a week**, or **Other…** and **Minutes between runs**, at least 1. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Reload nginx +processes: + "nginx: master.*" signals => { "hup" }; +``` diff --git a/content/policy-builder/blocks/report-blocks.markdown b/content/policy-builder/blocks/report-blocks.markdown new file mode 100644 index 000000000..8ca443c4e --- /dev/null +++ b/content/policy-builder/blocks/report-blocks.markdown @@ -0,0 +1,32 @@ +--- +layout: default +title: Report blocks +sorting: 90 +# Generated by scripts/docs-blocks.mjs in cfengine/policy-builder, from its block descriptors: edit those, not this page. +--- + +Blocks that print messages in the agent’s output. + +## Report Message + +Prints a message (prefixed "R:") to the agent's output, or appends it to a file. + +**Generates:** a `reports` promise. + +### Settings + +| Setting | Required | Description | +| ------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Message** | Yes | The text the agent prints. $(…) inserts a variable, e.g. $(sys.fqhost) for the host's name. | +| **Report to file** | No | Absolute path to append this message to, instead of printing it. Leave blank to print. | +| **How often** | No | Appending to a file every run grows it without limit. One of: **Every agent run**, **At most once an hour**, **At most once a day**, **At most once a week**, or **Other…** and **Minutes between runs**, at least 1. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Report provisioning +reports: + "Provisioned $(sys.fqhost)"; +``` diff --git a/content/policy-builder/blocks/service-blocks.markdown b/content/policy-builder/blocks/service-blocks.markdown new file mode 100644 index 000000000..6a3bf6c7f --- /dev/null +++ b/content/policy-builder/blocks/service-blocks.markdown @@ -0,0 +1,31 @@ +--- +layout: default +title: Service blocks +sorting: 60 +# Generated by scripts/docs-blocks.mjs in cfengine/policy-builder, from its block descriptors: edit those, not this page. +--- + +Blocks that keep services running, stopped, enabled or disabled. + +## Manage Service + +Starts, stops, reloads or restarts a service, or sets whether it starts at boot. + +**Generates:** a `services` promise. + +### Settings + +| Setting | Required | Description | +| ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Service name** | Yes | The service's name, as the host's service manager knows it, e.g. nginx or ssh. | +| **Action** | Yes | Reload and Restart act every time this block runs: connect them with a "repaired" arrow so they only run after a change. One of: **Running (and enabled at boot)**, **Stopped (and disabled at boot)**, **Enabled at boot (not started now)**, **Disabled at boot (not stopped now)**, **Reload**, **Restart**. Default: **Running (and enabled at boot)**. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Keep nginx running +services: + "nginx" service_policy => "start"; +``` diff --git a/content/policy-builder/blocks/storage-blocks.markdown b/content/policy-builder/blocks/storage-blocks.markdown new file mode 100644 index 000000000..419d889fc --- /dev/null +++ b/content/policy-builder/blocks/storage-blocks.markdown @@ -0,0 +1,41 @@ +--- +layout: default +title: Storage blocks +sorting: 80 +# Generated by scripts/docs-blocks.mjs in cfengine/policy-builder, from its block descriptors: edit those, not this page. +--- + +Blocks that mount file systems. + +## Mount Storage + +Ensures a network filesystem is mounted, and in the filesystem table. + +**Generates:** a `storage` promise. + +### Settings + +| Setting | Required | Description | +| ------------------- | -------- | ----------------------------------------------------------------------- | +| **Mount point** | Yes | Where the share is mounted on the host, e.g. /mnt/share. | +| **Server** | Yes | Hostname or IP of the file server. | +| **Remote path** | Yes | The exported path on the server, e.g. /export/data. | +| **Filesystem type** | Yes | One of: `nfs`, `nfs2`, `nfs3`, `nfs4`, `panfs`, `cifs`. Default: `nfs`. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Mount the share +storage: + "/mnt/share" mount => remote_mount("nfs", "nas.local", "/exports/share"); + +body mount remote_mount(type, server, source) +{ + mount_type => "$(type)"; + mount_server => "$(server)"; + mount_source => "$(source)"; + edit_fstab => "true"; +} +``` diff --git a/content/policy-builder/blocks/user-blocks.markdown b/content/policy-builder/blocks/user-blocks.markdown new file mode 100644 index 000000000..4d487446b --- /dev/null +++ b/content/policy-builder/blocks/user-blocks.markdown @@ -0,0 +1,31 @@ +--- +layout: default +title: User blocks +sorting: 70 +# Generated by scripts/docs-blocks.mjs in cfengine/policy-builder, from its block descriptors: edit those, not this page. +--- + +Blocks that create, change and remove local user accounts. + +## Manage Users + +Ensures a user account is present, locked, or absent. + +**Generates:** an `users` promise. + +### Settings + +| Setting | Required | Description | +| ------------ | -------- | ------------------------------------------------------------------ | +| **Username** | Yes | Lowercase letters, numbers, underscore, and hyphen. | +| **State** | Yes | One of: **Present**, **Locked**, **Absent**. Default: **Present**. | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +# Create the deploy user +users: + "deploy" policy => "present"; +``` diff --git a/content/policy-builder/blocks/variable-and-class-blocks.markdown b/content/policy-builder/blocks/variable-and-class-blocks.markdown new file mode 100644 index 000000000..7ea9308cf --- /dev/null +++ b/content/policy-builder/blocks/variable-and-class-blocks.markdown @@ -0,0 +1,93 @@ +--- +layout: default +title: Variable and class blocks +sorting: 30 +# Generated by scripts/docs-blocks.mjs in cfengine/policy-builder, from its block descriptors: edit those, not this page. +--- + +Blocks that define variables (values other blocks can use) and classes (conditions other blocks can depend on). + +## Define Class + +Defines one or more classes from conditions, for other blocks to use. + +**Generates:** a `classes` promise. + +### Settings + +| Setting | Required | Description | +| -------------- | -------- | ----------------------------------------------------------------------------------------- | +| **Class name** | Yes | Letters, numbers, and underscores only — and not the name of a built-in class like linux. | + +### Sources + +Where the class comes from: + +| Source | CFEngine | Settings | +| --------------------------------- | ----------------- | ------------------------------- | +| **Always true** | `any` | — | +| **Always false** | `!any` | — | +| **All of these classes (AND)** | `and =>` | — | +| **Any of these classes (OR)** | `or =>` | — | +| **File exists** | `fileexists()` | File path | +| **Directory exists** | `isdir()` | Directory path | +| **Variable is defined** | `isvariable()` | Variable name | +| **Value matches pattern (regex)** | `regcmp()` | Pattern (regex), Value to match | +| **Values are equal** | `strcmp()` | First value, Second value | +| **Value is greater than** | `isgreaterthan()` | Value, Is greater than | +| **Value is less than** | `islessthan()` | Value, Is less than | +| **Command succeeds** | `returnszero()` | Command, Shell mode | +| **Custom expression** | — | Condition expression | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +classes: + # Roles + "nginx_installed" expression => fileexists("/usr/sbin/nginx"); +``` + +## Define Variable + +Defines one or more variables other blocks can reference. + +**Generates:** a `vars` promise. + +### Settings + +| Setting | Required | Description | +| ----------------- | -------- | --------------------------------------- | +| **Variable name** | Yes | Letters, numbers, and underscores only. | + +### Sources + +Where the variable comes from: + +| Source | CFEngine | Settings | +| ----------------------------- | ------------------ | ------------------------------------------------------------------------ | +| **Literal** | `string` | Value | +| **List** | `slist` | Items | +| **Per condition** | `ifelse()` | Values, Otherwise | +| **Structured data (literal)** | `parsejson()` | JSON | +| **Variable** | `$(…)` | Variable | +| **List variable** | `@(…)` | List variable | +| **Data variable** | `mergedata()` | Data variable | +| **Command output** | `execresult()` | Command, Shell mode, Capture | +| **File lines** | `readstringlist()` | File path, Skip lines matching, Split on (regex), Max entries, Max bytes | +| **File content** | `readfile()` | File path, Max bytes | +| **File metadata** | `filestat()` | File path, Attribute | +| **Environment variable** | `getenv()` | Environment variable name, Max characters | +| **Structured data (JSON)** | `readjson()` | File path, Max bytes | + +### Example + +The policy it generates, for example: + +```cf3 {skip} +vars: + # App settings + "app_port" string => "8080"; + "admins" slist => { "alice", "bob" }; +``` diff --git a/content/policy-builder/canvas.markdown b/content/policy-builder/canvas.markdown new file mode 100644 index 000000000..8d64a1193 --- /dev/null +++ b/content/policy-builder/canvas.markdown @@ -0,0 +1,152 @@ +--- +layout: default +title: Building policy on the canvas +sorting: 30 +--- + +A project's policy is split into configurations, and each configuration has its own canvas, where each block is one thing a host should have or do. +This page starts with configurations, then explains how to work with blocks, how to order them with arrows, how to make them apply only to some hosts, and how to group them. + +Shortcuts are written for macOS; on Windows and Linux, use Ctrl for ⌘ and Alt for ⌥. + +## Configurations + +A _configuration_ is one policy file of the project, with its own canvas of blocks. +The project's configurations are listed under **Configurations** in the side bar; click one to open its canvas. +(The **Files** category in the Blocks palette is something else: blocks that manage files on the hosts.) + +![The Configurations list](policy-builder-configurations.png) + +A new project starts with one configuration, named after the project. + +- The icons next to **Configurations** add a configuration or a folder. Type its name, and press Enter. +- Right-click a configuration or folder to rename or delete it, or drag it into another folder. +- Deleting a folder deletes the configurations in it. A project always keeps at least one configuration. +- Hover a configuration's name to read its description, and the number under it is its block count. + +Folders organize the list, and the project folder too: a configuration's policy file is written in the same folder in the project, such as `roles/webserver.cf`. + +### When to start a new configuration + +One configuration is enough for a small policy. Start a new one when: + +- **A set of blocks serves one role**, such as a web server or a database server. A configuration per role, with a [file condition](#configuration-settings) on the role's class, makes each role's policy easy to find and to switch on or off. +- **Several configurations share values**, such as a package name or a port. Put the Define Variable and Define Class blocks they share in a configuration of their own, like the demo's **Common.cf**, and refer to them from the others. +- **Something applies to every host**, such as hardening or a login banner: a baseline configuration, like the demo's **Security.cf**, keeps it apart from role-specific policy. +- **A canvas gets hard to follow.** Around fifteen to twenty blocks, splitting by purpose usually reads better than one long column. Groups help too, inside one configuration: see [Groups](#groups). + +Use folders once there are several of the same kind, such as `roles/` for role configurations and `baseline/` for what every host gets. + +### Namespaces + +Each configuration is a CFEngine _namespace_, named when it's created, such as `common` for **Common.cf**: renaming it later doesn't change the namespace, so choose the name with care. +Variables and classes are referred to by their name in the same configuration, and with the namespace in front from another one, such as `$(common:vars.webserver_package)`. +See [Namespaces][] for how CFEngine uses them. + +### Configuration settings + +With no block selected, Properties shows the open configuration's settings: + +- **Description**: what it does, shown when you hover its name and at the top of its generated policy. +- **File condition**: a class expression that guards every promise in the configuration, its variables and classes included, such as `webserver_role` for only the hosts with that class. A banner on the canvas shows it. + +## Blocks + +Add a block by clicking it in the **Blocks** palette or dragging it onto the canvas. +Click a block to select it and edit it in **Properties**: + +- **Label**: what the block achieves, short and specific, such as "Install nginx" or "Render nginx config". The label names the block in the generated policy too. +- **Note**: why the block is there. It's kept in the generated policy, as the promise's comment. +- The block's own settings, described in the [Block reference][]. +- **Condition** and **How often**, described below. + +![A block's properties](policy-builder-block-properties.png) + +Each block on the canvas shows its number in run order (`#1`, `#2`…) and the promise type it generates, such as `packages`. +To select several blocks, drag a box around them with ⇧ held (or with the **Select** tool), or press ⌘A to select every block. +⌘X, ⌘C and ⌘V cut, copy and paste blocks, also into another configuration, and ⌫ (Delete on Windows and Linux) deletes the selection. + +Each block is a _promise_: a statement about the host that the CFEngine agent checks on every run, by default every five minutes. +Each check ends in one of three _outcomes_: + +| Outcome | Meaning | +| ------------ | --------------------------------------------------------------------- | +| **kept** | Everything was already as it should be: nothing had to change. | +| **repaired** | The agent changed something to get there, for example rewrote a file. | +| **not kept** | It tried but failed, for example the package couldn't be installed. | + +## Arrows and outcomes + +Without arrows, blocks run in canvas order: top to bottom, then left to right. +An arrow from one block to another means: run the second block after the first, if the first ended with one of the arrow's outcomes. +Drag from the dot at the bottom of a block to the dot at the top of another to draw one. + +![Arrows between blocks](policy-builder-canvas-arrows.png) + +A new arrow waits for **kept** or **repaired**: "after the first block succeeded". +Some blocks start with **repaired** instead, because their success always means a change: Run Command, Cron Job, Render Template, Custom CFEngine, the blocks that edit a file's contents, and blocks that clean up or watch files. The [Block reference][] doesn't list them separately; the arrow's label shows what it waits for. +Click the label on an arrow to choose its outcomes; ticking several means any of them will do. + +![Choosing an arrow's outcomes](policy-builder-outcome-menu.png) + +Two patterns come up often: + +- **Reacting to a change**: put a restart or reload behind a **repaired** arrow from what changes the configuration, such as **Render Template** to **Manage Service** with the action restart. Without that arrow, the service restarts on every agent run. Properties warns about this. +- **Handling a failure**: a **not kept** arrow runs a block only when the one before it failed, for example a **Report Message** that says so. + +Arrows can't form loops. +Define Variable and Define Class blocks have no arrows: other blocks use their variables and classes instead. + +## Conditions + +A _class_ in CFEngine is a named true or false flag. +CFEngine sets many on every host by itself, such as `linux`, `debian`, `ubuntu_22`, `redhat`, `x86_64` or `Monday`; these are _hard classes_. +Your policy can define more, such as `webserver_role`, with a **Define Class** block. + +A condition limits a block to hosts where a class expression is true: + +1. Select the block, open **Condition** in Properties, and click **Add condition**. +2. Choose the **Mode**: **Run only if** the expression is true, or **Skip if** it is. +3. In **Trigger**, type the class expression. As you type, it suggests classes: the hard classes, each with what it means, and the ones your project defines. **New class** creates a class for the project, in a Define Class block. + +Class expressions combine classes: + +| Expression | True when | +| ------------------------- | ----------------------------------- | +| `debian` | the host runs Debian | +| `debian\|redhat` | the host runs Debian or Red Hat | +| `linux.!debian` | the host runs Linux, but not Debian | +| `(debian\|ubuntu).x86_64` | Debian or Ubuntu on x86-64 | + +A class defined in another configuration is written with that configuration's namespace in front, such as `common:webserver_role` (see [Namespaces](#namespaces)). +The canvas draws a condition as an **only if** or **skip if** label linked to the block. +See [Classes and decisions][] for more on classes. + +A condition decides _whether_ a block runs at all; an arrow decides _when_ it runs, relative to another block, and on which outcome. + +## Groups + +Select several blocks and press ⌘G to group them, then name the group in Properties; ⇧⌘G ungroups them. +A group is drawn as a frame, and runs as one step: in the generated policy, it's a bundle of its own. + +![A group of two blocks](policy-builder-group.png) + +- An arrow into a group runs the whole group after the source. +- An arrow out of a group reads the group's combined outcome: not kept if any of its blocks failed, else repaired if any of them changed something, else kept. +- A condition on a group applies to all its blocks. + +Use groups for blocks that belong together, such as "Configure nginx", and to gate them all with one condition. + +## How often + +A block runs on every agent run unless **How often** in Properties limits it to **at most once an hour**, **once a day** or **once a week**. +That suits work that doesn't need checking every few minutes, such as downloading a file or sending a report. + +Some blocks come with a limit already: **Run Command** runs at most once a day until you change it, because a command runs again every time it's checked. +Test environments ignore How often, so every test run runs every block. + +## Tidy, zoom and full screen + +- **Tidy up** in the canvas toolbar lays the open configuration out in run order, top to bottom. +- Zoom with + and - (or ⌘= and ⌘-), press 0 for actual size and 1 to fit the whole configuration. The minimap in the corner shows the whole configuration; drag in it to move around. +- The full screen button hides the side bar and Properties, to give the canvas the whole window. diff --git a/content/policy-builder/demo.markdown b/content/policy-builder/demo.markdown new file mode 100644 index 000000000..b9380658e --- /dev/null +++ b/content/policy-builder/demo.markdown @@ -0,0 +1,129 @@ +--- +layout: default +title: The demo project +sorting: 105 +--- + +Policy Builder comes with a demo: _Nginx Web Server Demo_, a complete project that turns a host into an nginx web server and hardens it. +It shows the parts of Policy Builder described in this section working together: several configurations, variables shared between them, arrows that react to changes, groups, templates and a module. +The demo isn't saved anywhere until you choose to, so change whatever you like. + +## Opening the demo + +On the start screen, click **Try Demo: Nginx Web Server** under **or explore**, or choose **File**, then **Try Demo: Nginx Web Server**. +It opens on **Webserver.cf**: + +![The demo project](policy-builder-window.png) + +The demo has three configurations, listed under **Configurations** in the side bar: + +| Configuration | Namespace | What it does | +| ---------------- | ----------- | ---------------------------------------------------------- | +| **Common.cf** | `common` | Values and a class the other configurations use. | +| **Webserver.cf** | `webserver` | Installs, configures and runs nginx, and publishes a page. | +| **Security.cf** | `security` | Baseline hardening for every host. | + +Each configuration runs its blocks in their numbered order, top to bottom. Below, each one is described block by block. + +## Common.cf + +![Common.cf on the canvas](policy-builder-demo-common.png) + +Common.cf holds what the other two configurations share, so it has no arrows: its blocks define values instead of acting on the host. + +- **Nginx settings** (Define Variable) defines three variables: + - `webserver_package`: `nginx`, the package to install. + - `worker_processes`: how many nginx worker processes to start, counted from the host's CPUs. It reads the lines of `/proc/cpuinfo`, keeps those matching `processor.*`, counts them, and falls back to `auto` if that fails. The card beside the block shows each of these transforms with its result on sample data. + - `worker_connections`: `1024`, the connections each worker accepts. +- **Web server role** (Define Class) defines the class `webserver_role`. Here it's always true, standing in for a real test of which hosts are web servers; the blocks that set up nginx run only where it's true. + +Webserver.cf uses these as `$(common:vars.webserver_package)` and `common:webserver_role`: the namespace `common`, then the name. See [Values and templates][]. + +## Webserver.cf + +![Webserver.cf on the canvas](policy-builder-demo-webserver.png) + +Webserver.cf sets up nginx, in run order: + +1. **Install web server package** (Install Package) installs `$(common:vars.webserver_package)`, only where `common:webserver_role` is true. Most of the file waits for it. +2. **Keep nginx running** (Manage Service) starts nginx and keeps it running, after the package is installed (arrow: kept or repaired). +3. **Render nginx config** (Render Template) writes `/etc/nginx/nginx.conf` from a template that uses `worker_processes` and `worker_connections`, after the package is installed. +4. **Restart nginx on config change** (Manage Service) restarts nginx, only when the config file was just rewritten: its arrow from Render nginx config waits for **repaired**. Without that arrow, nginx would restart on every agent run. +5. **Remove conflicting Apache** (Remove Package) removes `apache2`, which would compete for the same port, only on web servers. +6. **Create web root** (Create Directory) makes sure `/var/www/html` exists. +7. **Publish the demo landing page** (Render Template) writes `/var/www/html/index.html` from a template, after both the package and the web root (two arrows, kept or repaired). The page shows a **Web server role** badge on hosts where that class is true. +8. **Install python3** (Install Package) installs Python, which the next block's module needs, only on web servers. +9. **Download the CFEngine logo** (HTTP Request) downloads the logo for the landing page to `/var/www/html/cfengine-logo.svg`, after the web root exists and Python is installed. **How often** is once a day: the logo doesn't change, so there's no need to check every few minutes. Its promise type comes from the `promise-type-http` module, which the project lists under Modules. +10. **Remove default nginx site** (Remove File) removes `/etc/nginx/sites-enabled/default`. +11. **Lock down nginx config files** (Set Permissions) keeps `/etc/nginx/nginx.conf` and `/etc/nginx/conf.d/default.conf` at mode `644`, owned by root. +12. **Prune old nginx logs** (Clean Up Old Files) deletes files in `/var/log/nginx` untouched for 30 days. +13. **Create deploy user** (Manage Users) makes sure a `deploy` user exists. +14. **Report provisioning done** (Report Message) prints `Web server provisioning complete` in the agent's output. + +Blocks 10 to 14 have no arrows, so they simply run in order, every agent run. +See [Building policy on the canvas][] for arrows and conditions. + +## Security.cf + +![Security.cf on the canvas](policy-builder-demo-security.png) + +Security.cf hardens every host. It has no file condition, and its blocks are in two groups, each a bundle of its own in the generated policy: + +- **SSH hardening** group: + - **SSH server present** (Define Class) defines `sshd_installed`, true where `/etc/ssh/sshd_config` exists. + - **Harden SSH** (Set Config Values) sets `PermitRootLogin no`, `MaxAuthTries 3` and `X11Forwarding no` in `/etc/ssh/sshd_config`, only where `sshd_installed` is true, so hosts without an SSH server are left alone. + - **Restart SSH on config change** (Manage Service) restarts the `ssh` service, only when Harden SSH changed the file (arrow: repaired). +- **Account protection** group: + - **Lock down account files** (Set Permissions) keeps `/etc/passwd` and `/etc/group` at mode `644`, owned by root. + - **Protect password hashes** (Set Permissions) keeps `/etc/shadow` at mode `640`, owned by root and the `shadow` group. + - **Watch account files** (Watch File for Changes) watches `/etc/passwd`, `/etc/group` and `/etc/shadow` for changes. + - **Report account changes** (Report Message) reports `Local accounts changed on $(sys.fqhost)`, only when Watch account files saw a change (arrow: repaired). + +These settings suit the demo's Debian and Ubuntu test hosts. On Red Hat-family hosts the SSH service is `sshd`, and `/etc/shadow` has no `shadow` group: it's owned by root with mode `000`. + +**Login banner** (Ensure Lines in File), outside the groups, puts `Authorized access only. Activity may be monitored.` in `/etc/issue`. + +## Reading its policy + +Switch to **Generated Policy** in the top bar to read the CFEngine policy each configuration becomes. +Selecting a block highlights its lines. +[Generated policy][] explains how to read it. + +## Testing it + +If Docker runs on your computer, open **Tests & Logs** and click **Deploy & run**. +The demo comes with a test environment: one Ubuntu host, `web`, that is also its own hub, with port 8080 published. +The first run takes a few minutes, while Policy Builder downloads the host's image and CFEngine. +When it finishes, the host shows **Converged**: every promise was kept. + +![The demo, tested on its Ubuntu host](policy-builder-demo-test-converged.png) + +Open http://localhost:8080 to see the landing page the policy published, filled in by its template, with the CFEngine logo it downloaded: + +![The landing page the demo publishes](policy-builder-demo-landing-page.png) + +See [Test environments][]. + +## Seeing what problems look like + +The demo is written for Debian and Ubuntu, so a Red Hat-family host shows on purpose what a test with problems looks like. +Click **Add client host**, choose **RHEL 8 (AlmaLinux 8)**, and click **Deploy & run** again. + +![The demo on a RHEL 8 client: two problems](policy-builder-demo-test-problems.png) + +The client shows **2 problems**, and the list above the logs explains each one: the error, the block it comes from (or **Not traced to a block** when the log line doesn't say), and what the agent did just before it. +**Show block** selects the block on the canvas, and **Show in log** jumps to the lines in the log. + +- **Error installing package 'python3'**: on RHEL 8, `python3` is only a name the installed `python36` package answers to. + `dnf install python3` finds nothing to do, but CFEngine looks for a package called exactly `python3`, doesn't find one, and reports the install as failed. + To fix it, install the package by its RHEL 8 name there: in Common.cf, add a variable with the **Per condition** source, `python36` where `almalinux_8` holds (the test host's class for AlmaLinux 8) and `python3` otherwise, and use it as **Install python3**'s package name, as Install nginx uses `$(common:vars.webserver_package)`. +- **None of the promised groups for '/etc/shadow' exist**, from **Protect password hashes**: RHEL has no `shadow` group. + There `/etc/shadow` is owned by root, with mode `000`. + To fix it, give the block the condition `debian`, and add a second Set Permissions block with the condition `redhat` (true on RHEL and AlmaLinux) and their owner, group and mode. + +The same goes for **Restart SSH on config change**: on RHEL the service is `sshd`, not `ssh`. + +## Saving a copy + +To keep the demo, or start your own project from it, click **Save As…** and choose where to save it. +It then works like any other project: see [Policy Builder projects][]. diff --git a/content/policy-builder/deploying.markdown b/content/policy-builder/deploying.markdown new file mode 100644 index 000000000..800975803 --- /dev/null +++ b/content/policy-builder/deploying.markdown @@ -0,0 +1,77 @@ +--- +layout: default +title: Deploying from Policy Builder +sorting: 90 +--- + +When the policy is ready, Policy Builder gets it to your CFEngine hub, the policy server your hosts fetch their policy from. +Open **Deployment** in the top bar (⌘4). + +![The Deployment view, after a successful pre-flight](policy-builder-deployment-view.png) + +Deployment works on a project saved to a folder: a project that isn't saved yet, such as the demo, asks you to save it first. +A module project is deployed as part of the policy set that uses it. + +## Which way to deploy + +| Your hub | Deploy | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| CFEngine Enterprise | Through a git repository: push the project (**Git repository** tab), and the hub deploys what's pushed (**Enterprise hub** tab). Best for teams: every change is a commit. | +| CFEngine Community | Over SSH (**Hub over SSH** tab): Policy Builder copies the built policy set to the hub. | +| Enterprise, without git | Over SSH works with an Enterprise hub too. | + +Use one way per hub: a hub that deploys from git replaces policy copied over SSH on its next update. + +## Pre-flight checks + +The row at the top checks the project before it's deployed: + +| Check | Passes when | +| ---------------- | -------------------------------------------------------------------------------------------------- | +| **Saved** | The project has no unsaved edits. Pre-flight saves them for you. | +| **Valid policy** | The policy set builds with `cfbs build`, and the CFEngine linter and `cf-promises` find no errors. | +| **Tested** | The last run in a [test environment][Test environments] passed. A warning, not a blocker. | +| **Committed** | The project folder is a git repository with nothing left to commit. | +| **Pushed** | The repository's commits are pushed to its remote, and the remote has none you don't. | + +**Run pre-flight** runs them all. +Deploying runs them too, and stops if the policy has problems: **Valid policy** then lists them, with the blocks they come from. +If you change the project after a check, the view says so, and the next deploy checks again. + +## Git repository + +The **Git repository** tab commits the project and pushes it to a remote repository, such as one on GitHub or GitLab. + +1. If the project folder isn't a git repository yet, the tab offers to initialize one. +2. Set **Remote (origin)** to the repository to push to, such as `git@github.com:example/policy.git`, and click **Set**. Once a remote is set, **Settings** changes it. +3. Click **Commit & push**, write a commit message, and confirm. + +Pushing uses your own git credentials (your SSH agent or credential helper); Policy Builder stores none. +If the remote has commits you don't, the **Pushed** check offers two ways out: **Pull theirs, then push** replays your commits on top of theirs, pushes, and reloads the project; **Overwrite the remote** replaces their commits with yours. + +## Hub over SSH + +Policy Builder can copy the built policy set to a hub over SSH, as its masterfiles, with `cf-remote`. This works with Community and Enterprise hubs. + +1. On the **Hub over SSH** tab, open **Settings** and set the **Hub**, such as `root@hub.example.com`, its SSH **Port** (22 by default) and the **Private key** to sign in with. +2. Click **Deploy to** the hub: Policy Builder saves, builds and checks the policy set, then copies it to the hub, installs it and runs the hub's agent. + +The hub's hosts get the new policy on their next agent run, within about five minutes. + +## Enterprise hub + +A CFEngine Enterprise hub can deploy from a git repository: it pulls the repository, builds it with `cfbs` and deploys the result. See [Version control][] for how it works on the hub. +The **Enterprise hub** tab sets this up from Policy Builder, after the project is pushed (see [Git repository](#git-repository)). + +1. **Connect a hub…**: the URL of its Mission Portal (the hub's web interface), and a Mission Portal user name and password. +2. Choose **Point the hub at this project…** and check what the hub pulls: the **Repository the hub pulls**, its **Branch** and, if the project isn't at the repository's top, its **Subdirectory**. +3. Choose how the hub signs in to the repository under **Access**: + - **Public repository**: no credentials. + - **Username + token**: a user and an access token for the repository. + - **Deploy key**: the private half of an SSH key added to the repository as a deploy key, a key that only reads that one repository. An SSH repository URL needs one. + +Policy Builder saves these settings on the hub and turns on deploys from version control there. +If the hub already deploys another source, the tab says so and offers **Fix the hub's source…**. + +Then **Commit, push & deploy to hub** does everything in one go: it commits and pushes your changes, and runs the hub's agent, which pulls the repository, builds it, validates the policy and deploys it. +The tab shows which commit the hub runs, and the hub's hosts get the new policy on their next agent run. diff --git a/content/policy-builder/generated-policy.markdown b/content/policy-builder/generated-policy.markdown new file mode 100644 index 000000000..112fd2a39 --- /dev/null +++ b/content/policy-builder/generated-policy.markdown @@ -0,0 +1,99 @@ +--- +layout: default +title: Generated policy +sorting: 40 +--- + +Policy Builder writes CFEngine policy from your blocks. +You never have to edit it, but reading it shows exactly what your hosts will do, and it's a good way to learn the policy language. + +## The Generated Policy view + +Open **Generated Policy** in the top bar (⌘2) to read the open configuration's policy. + +![The Generated Policy view](policy-builder-generated-policy-view.png) + +- It follows your edits as you make them, before you save: **Up to date** shows it matches the canvas, **Updating…** that it's catching up. +- The lines of the selected block are highlighted, with the block's type next to its comment. +- References are links: click `$(common:vars.webserver_package)` to jump to where the variable is defined, and press ⌥← to go back. +- **Copy** copies the whole file. + +## From blocks to policy + +Here is part of the demo's **Webserver.cf**, as Policy Builder generates it. +Each configuration starts with its description and its namespace: + +```cf3 +############################################################################### +# +# Turns a webserver_role host into an nginx web server. It installs nginx, +# removes a conflicting Apache, renders nginx.conf from a template and restarts +# nginx only when that file changes. It keeps the service running and publishes +# a landing page, with the CFEngine logo downloaded from cfengine.com. It also +# tidies up: removes the default site, locks down config permissions, prunes +# old logs and creates a deploy user. +# +# Generated by CFEngine Policy Builder from "Webserver". +# Edits here are overwritten when the project is saved. +# +############################################################################### +body file control +{ + namespace => "webserver"; + evaluation_order => "top_down"; +} +``` + +`evaluation_order => "top_down"` makes CFEngine run the promises in the order they're written, which is the canvas's run order. It's new in CFEngine 3.27, so hosts need 3.27 or later: older agents refuse the policy (see [What you need][Policy Builder#What you need]). + +The blocks become promises in the file's `main` bundle, each under a comment with its label: + +```cf3 +bundle agent main +{ + # Install web server package + packages: + "$(common:vars.webserver_package)" + policy => "present", + if => "common:webserver_role", + classes => default:results("bundle", "install_web_server_package"); + + # Keep nginx running + services: + "nginx" + service_policy => "start", + if => "install_web_server_package_kept|install_web_server_package_repaired"; +} +``` + +Reading it: + +- `packages:` and `services:` are _promise types_: what kind of thing the promise is about. The quoted string under them, the _promiser_, is what the promise is about: the package, the service. +- `if => "common:webserver_role"` is the block's condition: only hosts where the class `webserver_role`, defined in **Common.cf**, is true. +- `classes => default:results("bundle", "install_web_server_package")` sets classes from the promise's outcome: `install_web_server_package_kept`, `_repaired` or `_not_kept`. Policy Builder adds it when an arrow leaves the block. +- The arrow to **Keep nginx running** became its `if`: the service is started only after the package promise ended kept or repaired. + +Some settings show up as attributes of the promise: + +- A note becomes `comment => "…"`. Promise types from modules don't take a comment, so there it's a `# Note:` line above the promise. +- **How often** becomes `action => default:if_elapsed("1440")`, the number of minutes between runs. + +Define Variable and Define Class blocks go into a `bundle common vars` in their configuration, which is why other blocks refer to their variables as `$(vars.name)`, or `$(namespace:vars.name)` from another file. +A group, a Custom CFEngine block and some blocks that use the standard library become bundles of their own, called from `main` with a `methods:` promise in their place. + +See [Promises][], [Promise types][] and [Bundles][] for the language itself. + +## Files and layout + +Each configuration is one `.cf` file in the project folder, at its place in the Configurations tree. +`cfbs.json` lists them, with their `main` bundles, so `cfbs build` adds them to the policy set and the agent runs each file's `main` bundle. +Templates of Render Template blocks go into `templates/`, shipped with the policy. + +## Checks + +Policy Builder checks the generated policy in two ways: + +- The **CFEngine linter** checks the text of Custom CFEngine blocks as you type. +- **cf-promises**, CFEngine's own policy checker, validates the built policy set during the Deployment view's pre-flight, under **Valid policy**. See [Deploying from Policy Builder][]. + +Problems found while testing, or by a hub that refuses the policy, are traced back to the block that caused them. diff --git a/content/policy-builder/installing.markdown b/content/policy-builder/installing.markdown new file mode 100644 index 000000000..15083ed2a --- /dev/null +++ b/content/policy-builder/installing.markdown @@ -0,0 +1,21 @@ +--- +layout: default +title: Installing Policy Builder +sorting: 10 +--- + +Policy Builder runs on macOS, Windows and Linux. +It includes everything it needs to build and check policy, so you don't need CFEngine installed on your computer. +Some features use other software, listed at the end of this page. + +## Downloading and installing + +Download Policy Builder from [cfengine.com/policy-builder](https://cfengine.com/policy-builder/#install). +The page offers the file for your computer, lists the files for the other systems, and gives the command that installs each Linux package. + +The first time you open Policy Builder on macOS, macOS asks to confirm opening an app downloaded from the internet: click **Open**. + +## Optional dependencies + +- **Docker**, for [test environments][Test environments]: Docker Desktop, Colima, OrbStack or Docker Engine. Policy Builder finds a running Docker on its own. +- **git**, for keeping a project in version control and [deploying it to a hub][Deploying from Policy Builder] from a repository, and for creating a project on the **Master branch** masterfiles. diff --git a/content/policy-builder/modules.markdown b/content/policy-builder/modules.markdown new file mode 100644 index 000000000..1070c00a8 --- /dev/null +++ b/content/policy-builder/modules.markdown @@ -0,0 +1,85 @@ +--- +layout: default +title: Modules in Policy Builder +sorting: 60 +--- + +[CFEngine Build](https://build.cfengine.com) has ready-made modules for inventory, compliance reports, security hardening, common tasks and new promise types. +Policy Builder can browse them, add them to a project and answer their questions, and installs them with `cfbs` when you save. + +Modules are for policy sets: a project of the module type can't add them (see [Policy Builder projects][]). + +## Browsing modules + +Click **Modules** in the activity bar (⇧⌘X) to open the Modules view. + +![The Modules view](policy-builder-modules-view.png) + +- **Search modules, promise types…** filters the list by name and description. +- The chips filter by tag: **Security**, **Inventory**, **Compliance**, **Management**, **Promise types**, **Windows**, **SSH** and **Libraries**. +- **Supported only** shows only the modules CFEngine supports. +- **In this project** lists the project's modules, in the order they run; **All modules** lists the rest. + +Libraries, modules that other modules need, are hidden unless you pick the **Libraries** chip, search for one or choose **Show libraries** in the **…** menu. +The same menu's **Refresh list** downloads the list again. + +Each module shows its version and badges: **Experimental** for modules not yet considered stable, **Asks for input** for modules with questions to answer, and **Promise type** for modules that add a promise type. + +The list comes from build.cfengine.com and is kept on your computer, so the view works offline with the last list downloaded; it says so, with the date. + +A search in the **Blocks** palette that finds no block offers **Browse modules…**, which searches the modules for the same words. + +## A module's page + +Click a module to open its page, as on build.cfengine.com: + +![A module's page](policy-builder-module-page.png) + +- **Description**: the module's README. +- **Dependencies**: the modules it needs, which are added with it. +- **Input**: the module's questions, when it has any. +- On the side: links to its repository and documentation, its maintainer, versions and download statistics. + +**Add to project** adds it, and **Remove** takes it out again. +**Back to Canvas**, or the **Blocks and files** icon, returns to the canvas. + +## Adding a module + +Click **Add** on a module in the list, or **Add to project** on its page. +Its dependencies are added with it, shown under **Requires** in its card. A dependency that saving will add shows **on save**. + +Nothing is installed until you save: saving runs `cfbs add` for new modules and `cfbs remove` for removed ones, so it needs internet access and can take a while. +Modules run before the project's own policy, in the order listed under **In this project**. +They aren't blocks and have no arrows. + +## Answering a module's input + +Some modules ask questions before they can work, such as delete-files, which asks which files to delete. +Until every question is answered: + +- the Modules icon in the activity bar shows how many modules need input; +- the module's card has an orange border and an **Answer its input** button, and a line above the list names it; +- the **Modules** row under Configurations says how many need input. + +Answer on the module page's **Input** tab: a question takes text, a list of texts, or a list of rows with several fields each. + +![A module's Input tab](policy-builder-module-input.png) + +The answers are saved in the project, in the module's `input.json`, and undo takes an answer back like any other change. + +## Blocks from modules + +Four blocks use promise types that come from CFEngine Build modules: **Git Checkout**, **HTTP Request**, **Systemd Unit** and **Run Ansible Playbook**. [Blocks from modules][] lists their settings, and which module each needs. + +Adding one of these blocks adds its module to the project, and the module stays while a block uses it. +Prefer them to running commands: they report kept and repaired properly, so arrows and How often work. + +These modules run with Python, so hosts need `python3` (test hosts have it). +Properties advises adding an **Install Package** block for `python3` before such a block: **Add Install python3 before it** adds one, connected with an arrow, and **Ignore in this project** stops the advice if your hosts already have Python. + +## Removing a module + +Choose **Remove…** in a module's gear menu, or **Remove** on its page. +Its dependencies go with it, unless another module still needs them. + +A module can't be removed while it's needed: a module a block uses shows **Used by** and the block's label, and a dependency shows **Added for** the module that needs it. diff --git a/content/policy-builder/policy-builder-agent-working.png b/content/policy-builder/policy-builder-agent-working.png new file mode 100644 index 000000000..56aeebbf6 Binary files /dev/null and b/content/policy-builder/policy-builder-agent-working.png differ diff --git a/content/policy-builder/policy-builder-block-properties.png b/content/policy-builder/policy-builder-block-properties.png new file mode 100644 index 000000000..5e3b84b0b Binary files /dev/null and b/content/policy-builder/policy-builder-block-properties.png differ diff --git a/content/policy-builder/policy-builder-canvas-arrows.png b/content/policy-builder/policy-builder-canvas-arrows.png new file mode 100644 index 000000000..4f2827c16 Binary files /dev/null and b/content/policy-builder/policy-builder-canvas-arrows.png differ diff --git a/content/policy-builder/policy-builder-configurations.png b/content/policy-builder/policy-builder-configurations.png new file mode 100644 index 000000000..688b475b4 Binary files /dev/null and b/content/policy-builder/policy-builder-configurations.png differ diff --git a/content/policy-builder/policy-builder-connect-agent-dialog.png b/content/policy-builder/policy-builder-connect-agent-dialog.png new file mode 100644 index 000000000..9215e990c Binary files /dev/null and b/content/policy-builder/policy-builder-connect-agent-dialog.png differ diff --git a/content/policy-builder/policy-builder-demo-common.png b/content/policy-builder/policy-builder-demo-common.png new file mode 100644 index 000000000..f34e2317e Binary files /dev/null and b/content/policy-builder/policy-builder-demo-common.png differ diff --git a/content/policy-builder/policy-builder-demo-landing-page.png b/content/policy-builder/policy-builder-demo-landing-page.png new file mode 100644 index 000000000..7968605de Binary files /dev/null and b/content/policy-builder/policy-builder-demo-landing-page.png differ diff --git a/content/policy-builder/policy-builder-demo-security.png b/content/policy-builder/policy-builder-demo-security.png new file mode 100644 index 000000000..5822822e1 Binary files /dev/null and b/content/policy-builder/policy-builder-demo-security.png differ diff --git a/content/policy-builder/policy-builder-demo-test-converged.png b/content/policy-builder/policy-builder-demo-test-converged.png new file mode 100644 index 000000000..f7d6bb845 Binary files /dev/null and b/content/policy-builder/policy-builder-demo-test-converged.png differ diff --git a/content/policy-builder/policy-builder-demo-test-problems.png b/content/policy-builder/policy-builder-demo-test-problems.png new file mode 100644 index 000000000..30c278a0b Binary files /dev/null and b/content/policy-builder/policy-builder-demo-test-problems.png differ diff --git a/content/policy-builder/policy-builder-demo-webserver.png b/content/policy-builder/policy-builder-demo-webserver.png new file mode 100644 index 000000000..9de199b90 Binary files /dev/null and b/content/policy-builder/policy-builder-demo-webserver.png differ diff --git a/content/policy-builder/policy-builder-deployment-view.png b/content/policy-builder/policy-builder-deployment-view.png new file mode 100644 index 000000000..9cc29df65 Binary files /dev/null and b/content/policy-builder/policy-builder-deployment-view.png differ diff --git a/content/policy-builder/policy-builder-generated-policy-view.png b/content/policy-builder/policy-builder-generated-policy-view.png new file mode 100644 index 000000000..668b29ba1 Binary files /dev/null and b/content/policy-builder/policy-builder-generated-policy-view.png differ diff --git a/content/policy-builder/policy-builder-group.png b/content/policy-builder/policy-builder-group.png new file mode 100644 index 000000000..4b640de6b Binary files /dev/null and b/content/policy-builder/policy-builder-group.png differ diff --git a/content/policy-builder/policy-builder-host-settings.png b/content/policy-builder/policy-builder-host-settings.png new file mode 100644 index 000000000..501eeabc2 Binary files /dev/null and b/content/policy-builder/policy-builder-host-settings.png differ diff --git a/content/policy-builder/policy-builder-module-input.png b/content/policy-builder/policy-builder-module-input.png new file mode 100644 index 000000000..36614bde0 Binary files /dev/null and b/content/policy-builder/policy-builder-module-input.png differ diff --git a/content/policy-builder/policy-builder-module-page.png b/content/policy-builder/policy-builder-module-page.png new file mode 100644 index 000000000..4bcd99aa4 Binary files /dev/null and b/content/policy-builder/policy-builder-module-page.png differ diff --git a/content/policy-builder/policy-builder-modules-view.png b/content/policy-builder/policy-builder-modules-view.png new file mode 100644 index 000000000..751b69e54 Binary files /dev/null and b/content/policy-builder/policy-builder-modules-view.png differ diff --git a/content/policy-builder/policy-builder-new-project-dialog.png b/content/policy-builder/policy-builder-new-project-dialog.png new file mode 100644 index 000000000..1f06f404c Binary files /dev/null and b/content/policy-builder/policy-builder-new-project-dialog.png differ diff --git a/content/policy-builder/policy-builder-outcome-menu.png b/content/policy-builder/policy-builder-outcome-menu.png new file mode 100644 index 000000000..8cb30cc71 Binary files /dev/null and b/content/policy-builder/policy-builder-outcome-menu.png differ diff --git a/content/policy-builder/policy-builder-template-editor.png b/content/policy-builder/policy-builder-template-editor.png new file mode 100644 index 000000000..4cab0efec Binary files /dev/null and b/content/policy-builder/policy-builder-template-editor.png differ diff --git a/content/policy-builder/policy-builder-tests-and-logs-view.png b/content/policy-builder/policy-builder-tests-and-logs-view.png new file mode 100644 index 000000000..0e0b34099 Binary files /dev/null and b/content/policy-builder/policy-builder-tests-and-logs-view.png differ diff --git a/content/policy-builder/policy-builder-transforms.png b/content/policy-builder/policy-builder-transforms.png new file mode 100644 index 000000000..57f05a2ad Binary files /dev/null and b/content/policy-builder/policy-builder-transforms.png differ diff --git a/content/policy-builder/policy-builder-window-parts.png b/content/policy-builder/policy-builder-window-parts.png new file mode 100644 index 000000000..ded05bb94 Binary files /dev/null and b/content/policy-builder/policy-builder-window-parts.png differ diff --git a/content/policy-builder/policy-builder-window.png b/content/policy-builder/policy-builder-window.png new file mode 100644 index 000000000..f11ec4275 Binary files /dev/null and b/content/policy-builder/policy-builder-window.png differ diff --git a/content/policy-builder/projects.markdown b/content/policy-builder/projects.markdown new file mode 100644 index 000000000..8981650b3 --- /dev/null +++ b/content/policy-builder/projects.markdown @@ -0,0 +1,93 @@ +--- +layout: default +title: Policy Builder projects +sorting: 20 +--- + +Everything in Policy Builder happens in a project. +This page explains what a project is and what each field of **New Project** means, then how to open, change and save projects. +To explore a complete project first, open [The demo project][]. + +## What a project is + +A Policy Builder project is a folder on your computer holding a CFEngine Build project, the same kind the `cfbs` command line tool works with. +It contains your blocks, the CFEngine policy generated from them, and what's needed to build that policy into something a hub can deploy. + +Most projects are **policy sets**: a complete policy for your hosts, made of masterfiles (the standard policy every CFEngine installation starts from) plus your own. +[Project types](#project-types), at the end of this page, explains the other type, modules. + +## Creating a project + +Click **New Project** on the start screen, or choose **File**, then **New Project…** (⌘N). + +![The New Project dialog](policy-builder-new-project-dialog.png) + +| Field | What it is | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Project name** | What the project is called, such as `Web servers`. The project's folder is named after it, in lowercase with dashes (`web-servers`), and so is its first configuration. **The name can't be changed later**: it also becomes that configuration's namespace (`web_servers`), the prefix other configurations use to refer to its variables and classes. | +| **Description** (required) | A sentence on what the project is for, written into `cfbs.json`. | +| **Location** | The folder the project's folder is created in. The dialog shows the full path it will create, and says if it can't create it there. | +| **Policy set** or **Module** | The project's type. Keep **Policy set** unless you're making a module for other projects: see [Project types](#project-types). | +| **Masterfiles version** | For a policy set: **Latest**, the newest released version, which suits most projects, or **Master branch**, the development version, for trying what's coming next. | +| **Initialize a git repository** | Makes the project folder a git repository, with one initial commit. Keep it to track changes and to [deploy to a hub][Deploying from Policy Builder] from git. | + +Click **Create**. +Creating a policy set downloads masterfiles, so it needs internet access and can take a moment; **Master branch** and **Initialize a git repository** also need git installed. +If creating fails, the dialog says why, and **Details** shows the full output. Nothing is left behind, so you can try again. + +The new project opens with one empty configuration, named after the project, ready for blocks. + +## Opening a project + +Click **Open Project…** on the start screen, choose **File**, then **Open Project…** (⌘O), or pick one of the recent projects. +Choose the project's folder, the one with `cfbs.json` in it. + +You can open a `cfbs` project that wasn't made in Policy Builder, too. +Its masterfiles, modules and other settings are kept when you save, but policy files written by hand don't become blocks: Policy Builder only shows and edits what it generates. + +**File**, then **Reload from Disk** reads the project again, for example after pulling changes made elsewhere into its folder. + +## Saving, undo and redo + +**Save** (⌘S) writes the project to its folder and generates its policy. +It also adds and removes the project's CFEngine Build modules with `cfbs`, which can take a while: the **Save** button and the status bar show **Saving…** until it's done. +A dot after the project's name shows unsaved changes, and Policy Builder asks before closing a project that has them. + +Every change on the canvas and in Properties can be undone (⌘Z) and redone (⇧⌘Z, or Ctrl+Y on Windows and Linux); typing in one field counts as one change. +Opening or creating a project starts a new undo history. + +## Project settings + +Open them from the gear icon next to the project's name, the icon at the bottom of the activity bar, or **File**, then **Project Settings…** (⌘,): + +- **Type**: change a policy set into a module or back. Saving converts `cfbs.json`; the other entries in it, masterfiles included, are kept. +- **Masterfiles**: the version a policy set builds on. + +## Project types + +| Type | What it is | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Policy set** | A complete policy for your hosts: masterfiles plus your own policy, built with `cfbs build` and deployed to a hub as a whole. | +| **Module** | A reusable piece of policy, for other policy sets to add to themselves. It has no masterfiles of its own, since the policy set that adds it brings them, and it's tested and deployed as part of a policy set that uses it. A module can't add CFEngine Build modules of its own. | + +**Note:** "module" here means a project type. CFEngine Build modules, the ready-made modules from build.cfengine.com, are something you add to a policy set: see [Modules in Policy Builder][]. + +## What a project folder contains + +| Path | What it is | +| ---------------------------------------- | ---------------------------------------------------------------------------- | +| `cfbs.json` | The CFEngine Build project: its masterfiles, modules and policy files. | +| `*.cf` | The generated policy, one file per configuration. | +| `.policy-builder/project.json` | The blocks, arrows, groups and canvas layout: what Policy Builder opens. | +| `.git/` | The git repository, if you chose one. | +| `templates/` | Templates of Render Template blocks, once the project has any. | +| `/input.json` | Your answers to a CFEngine Build module's questions, once you've given them. | +| `.policy-builder/test-environments.json` | The project's test environments, once it has any. | + +**Note:** don't edit the generated files (the `.cf` files, Policy Builder's entries in `cfbs.json`, and `templates/`) by hand. +Saving writes them again from the blocks, and your edits are lost. + +## Next steps + +- [The demo project][]: a complete project to explore. +- [Building policy on the canvas][]: blocks, arrows, conditions and groups. diff --git a/content/policy-builder/test-environments.markdown b/content/policy-builder/test-environments.markdown new file mode 100644 index 000000000..ca73b3b5b --- /dev/null +++ b/content/policy-builder/test-environments.markdown @@ -0,0 +1,75 @@ +--- +layout: default +title: Test environments +sorting: 80 +--- + +A test environment runs your policy on Docker containers on your own computer, before it reaches real hosts. +Each container is a host with real CFEngine installed: Policy Builder builds the policy set, deploys it to the environment's hub and runs the agent on every host, then shows what happened, block by block. + +## Requirements + +Test environments need Docker running on your computer: Docker Desktop, Colima, OrbStack or Docker Engine. +Policy Builder finds it on its own; while it looks, the view says **Checking Docker…**, and it tells you if Docker isn't installed or isn't running. + +The first run downloads the platform's image and the CFEngine package, so it needs internet access and takes a few minutes. Later runs reuse them. + +## Hosts + +Open **Tests & Logs** (⌘3). A project without a test environment offers **Create test environment**, which adds one host that is also its own hub. + +![The Tests & Logs view](policy-builder-tests-and-logs-view.png) + +The environment's hosts are listed under **Topology nodes**: + +- The **hub** host, marked **HUB**, is the policy server: it receives the policy set and the other hosts fetch it from there, as on a real CFEngine installation. +- **Add client host** adds a host that bootstraps to the hub: it registers with it, as a real host does when CFEngine is installed on it, and from then on fetches its policy there. +- **Add host** picks a host's platform: Ubuntu 20.04, 22.04 or 24.04, Debian 12 or 13, or RHEL 7, 8, 9 or 10. RHEL itself isn't freely available as an image, so those hosts run CentOS 7 and AlmaLinux 8 to 10, which are built from the same sources. **Add a host from another image** uses any Docker image, with the CFEngine package of the platform you choose. + +Each host's gear icon opens its **Host settings**: its name, platform or image, its role (the hub, which serves the policy, or a client), environment variables, and ports published from your computer to the container, such as `localhost:8080` to the container's port 80 to look at a web server's page. +Changing the platform, image or ports recreates the container on the next run. + +The settings icon at the top right opens the environment's settings: + +| Setting | What it is | +| ------------------------------- | --------------------------------------------------------------------------- | +| **Name** | The environment's name. | +| **Edition** | **Community** or **Enterprise** CFEngine. | +| **CFEngine version** | `latest`, or a version such as `3.27.1`. | +| **Architecture** | The hosts' processor architecture. | +| **Agent runs per Deploy & run** | At most this many runs per host: runs repeat until one repairs nothing. | +| **Environment variables** | Variables set on all hosts. | +| **Secrets file (.env)** | Optional. A file with variables kept out of the project, such as passwords. | + +## Deploy and run + +**Deploy & run** starts the hosts (creating and setting them up the first time), builds the policy set with masterfiles, deploys it to the hub and runs the agent on every host. +The play button on a host deploys and runs on that host only; **Stop** stops the containers but keeps what's installed on them. + +The agent runs several times in a row, until a run repairs nothing: later runs settle what earlier ones changed. +A host's status then shows how it ended: + +- **Converged**: the last run had nothing left to change. The policy is stable on this host. +- **Still repairing**: the last run still changed something, so some promise changes the host every time, such as a command that always runs or a file rewritten on every run. The host names the blocks to fix. +- **Failed**: the run didn't finish, for example because the hub refused the policy. + +Test runs ignore How often, so every block runs every time. + +## Logs and problems + +The log on the right shows every host's output, or one host's with its tab. **Filter logs…** searches it. + +Problems, such as promises not kept or policy errors, are listed above the log with the block they came from. +Click the block's name to show it on the canvas, where you can fix it, or the problem's link to find it in the log. +A problem that can't be traced to a block says **Not traced to a block**. + +## Commands and shells + +The bar under the log runs a command on all hosts or one, such as `/var/cfengine/bin/cf-agent -KI` to run the agent by hand, or `cat /etc/nginx/nginx.conf` to look at a file the policy wrote. +A host's terminal icon picks that host in the bar. + +For an interactive shell, open a running host's settings with its gear icon: **Shell in this container** shows the `docker exec` command that opens one, to copy into a terminal. + +![A running host's settings, with the command for a shell in it](policy-builder-host-settings.png) + +**Purge all**, under the hosts, removes the environment's containers and their network. The environment's settings stay in the project, so the next run creates them again. diff --git a/content/policy-builder/values-and-templates.markdown b/content/policy-builder/values-and-templates.markdown new file mode 100644 index 000000000..b4c1e4641 --- /dev/null +++ b/content/policy-builder/values-and-templates.markdown @@ -0,0 +1,101 @@ +--- +layout: default +title: Values and templates +sorting: 50 +--- + +Blocks can take their values from variables instead of fixed text, so one change in one place updates every block that uses it. +This page explains variables and classes, where their values can come from, how to transform them, and how to render files from templates. + +## Variables, lists and data + +A _variable_ is a named value. CFEngine has a few kinds: + +| Kind | Holds | Example | +| -------------- | ------------------------------------- | ------------------- | +| String | One piece of text | `nginx` | +| List (`slist`) | Several pieces of text | `{ "curl", "git" }` | +| Data | Structured data, like a JSON document | `{ "port": 8080 }` | + +Variables are defined with a **Define Variable** block, and Policy Builder puts all of a configuration's variables in one bundle named `vars`. +That's why a block refers to a variable as `vars.` and its name: + +- `$(vars.name)` for a variable defined in the same configuration. +- `$(common:vars.name)` for one defined in another configuration, here the one whose namespace is `common`: namespace, then `vars`, then the name. +- A list can be used where a block takes one value per line, such as **Package name**: the block then acts on each item. + +Fields that take a variable or class name, such as a condition's **Trigger**, suggest names as you type, already written with the namespace they need. +See [Variables][] for how CFEngine handles them. + +## Defining variables and classes + +A **Define Variable** block holds one or more variable definitions, called entries; a **Define Class** block holds class definitions. +Add the block, then add entries in Properties: each one has a name, a source (where its value comes from) and, optionally, a condition. + +Their variables and classes belong to the configuration they're in. +Put values several files share in one configuration of their own, as the demo does with **Common.cf**. + +A variable entry can also be reported to CFEngine Enterprise's Inventory: turn on **Report variable** and give it an **Attribute name**, and Mission Portal's Inventory reports show its value for every host. + +## Where a value comes from + +An entry's source sets how its value is found on each host. +For example, a variable can be: + +- typed in: **Literal**, **List**, or **Structured data (literal)**; +- different per host: **Per condition**, a value for each class expression, plus one for **Otherwise**; +- read from the host: **Command output**, **File content**, **File lines**, **File metadata**, **Environment variable** or **Structured data (JSON)** from a file; +- another variable: **Variable**, **List variable** or **Data variable**. + +A class can be always true or false, combine other classes (**All of these classes (AND)**, **Any of these classes (OR)**), or test the host: a file or directory exists, a command succeeds, a value matches a pattern or compares with another. + +The [Variable and class blocks][] reference lists every source with the CFEngine function it uses. + +## Transforms + +A value read from the host is often not quite what a block needs. +**Add transform** on an entry's value chains steps that change it, each one taking the result of the step before: + +- **String transforms**, such as: extract or replace with a regular expression, trim, change case, take the first or last characters, format into a template, use a default if empty. +- **List transforms**, such as: split a string into a list, keep matching entries, sort, remove duplicates, remove entries, join into a string, pick an entry, count entries. +- **Data transforms**, such as: keys, values, a field, merge with JSON, as JSON text, count entries. + +The demo counts the host's CPUs this way: the **File lines** of `/proc/cpuinfo`, then **Keep matching entries** `processor.*`, then **Count entries**, then **Default if empty** `auto`. +The canvas shows a variable's transforms as a card next to its block, with each step's result on sample data: + +![A variable's transforms, on the canvas](policy-builder-transforms.png) + +Each step must accept what the step before it produces: a list transform needs a list, so split a string into a list first. +**Preview transform** runs the steps on a sample input you type, so you can check the result before the policy runs. +Typed-in values (**Literal**, **List**, **Per condition** and **Structured data (literal)**) take no transforms. + +A block's own setting can be computed the same way: under a setting that offers it, click **compute … from data…** to take its value from a source and transforms instead of typed-in text. + +## Templates + +A template is a file with placeholders that are filled in on each host, such as a configuration file with the right number of worker processes. +Policy Builder uses Mustache templates, CFEngine's built-in template language, with the **Render Template** block: + +1. Add a **Render Template** block and set the **Destination path**, the file it writes on the host. +2. Click **Template content** to open the template editor. +3. Write the file, and insert variables and classes from the list next to the editor: drag them onto the editor, click them, or type `@` for variables and `#` for classes. + +![The template editor](policy-builder-template-editor.png) + +In a template, values are looked up by path, starting with `vars` for variables and `classes` for classes: + +- A variable is inserted as `{{{vars.common:vars.worker_processes}}}`: `vars.`, then the variable's own reference, `common:vars.worker_processes` (namespace, the `vars` bundle, its name). The triple braces insert the value as it is. +- A class is a section, so its text appears only on hosts where the class is true. A hard class has no namespace, a class from the project has its configuration's: + +``` +{{#classes.debian}} +user www-data; +{{/classes.debian}} +{{#classes.common:webserver_role}} +# This host is a web server. +{{/classes.common:webserver_role}} +``` + +When saving, Policy Builder stores the templates in the project's `templates/` folder and ships them with the policy. +Connect a **Manage Service** block with a **repaired** arrow from the Render Template block to restart the service only when the file changed. +See [Rendering files with Mustache templates][] for the full template language.