From 5576f74c3a35cc455a30629fa53ac31a649866c0 Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Thu, 21 May 2026 00:34:00 +0200 Subject: [PATCH 1/7] Made autolinking fail loudly so broken links are exposed Co-authored-by: Claude Opus 4.7 (1M context) Signed-off-by: Ole Herman Schumacher Elgesem --- .../_scripts/cfdoc_references_resolver.py | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/generator/_scripts/cfdoc_references_resolver.py b/generator/_scripts/cfdoc_references_resolver.py index 4f6071936..64a36810d 100644 --- a/generator/_scripts/cfdoc_references_resolver.py +++ b/generator/_scripts/cfdoc_references_resolver.py @@ -35,8 +35,12 @@ def load_references(references_file): return references -def process(file_path, references): - """Process a markdown file and replace reference links with direct links.""" +def process(file_path, references, missing): + """Process a markdown file and replace reference links with direct links. + + Any reference that cannot be resolved is appended to `missing` as a + (file_path, ref) tuple so the caller can fail the build. + """ with open(file_path, "r", encoding="utf-8") as f: content = f.read() @@ -59,9 +63,7 @@ def replace_link(match): else: return f"[{text}]({url})" else: - sys.stderr.write( - f"References {ref} is not found in the _references.md. File: {file_path}\n" - ) + missing.append((file_path, ref)) return match.group(0) new_content = re.sub(pattern, replace_link, content) @@ -81,9 +83,7 @@ def replace_function_link(match): else: return f"[{text}]({url})" else: - sys.stderr.write( - f"References {ref} is not found in the _references.md. File: {file_path}\n" - ) + missing.append((file_path, f"{ref}()")) return match.group(0) new_content = re.sub(functions_pattern, replace_function_link, new_content) @@ -93,9 +93,27 @@ def replace_function_link(match): def run(config): - """Replaces [text][reference] with markdown links retrieved from _references.md""" + """Replaces [text][reference] with markdown links retrieved from _references.md. + + Exits non-zero if any reference cannot be resolved, so broken autolinks + fail the build loudly instead of silently shipping unresolved markdown. + """ markdown_files = config["markdown_files"] references = load_references("documentation/generator/_references.md") + missing = [] for file in markdown_files: - process(file, references) + process(file, references, missing) + + if missing: + sys.stderr.write( + "ERROR: %d unresolved reference link(s) found in _references.md:\n" + % len(missing) + ) + for file_path, ref in missing: + sys.stderr.write(f" [{ref}] in {file_path}\n") + sys.stderr.write( + "Add the missing entries to documentation/generator/_references.md " + "or fix the links in the listed files.\n" + ) + sys.exit(1) From feffc4e07acf7e776493ad9e9ea860c42bb90a85 Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Thu, 21 May 2026 01:08:49 +0200 Subject: [PATCH 2/7] Made a script to build docs locally Co-authored-by: Claude Opus 4.7 (1M context) Signed-off-by: Ole Herman Schumacher Elgesem --- .gitignore | 3 ++ build-locally.sh | 131 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100755 build-locally.sh diff --git a/.gitignore b/.gitignore index 4e585782d..17db7b7d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Build artifacts cfdoc_log.markdown +/tmp/ +/output/ + # emacs *~ diff --git a/build-locally.sh b/build-locally.sh new file mode 100755 index 000000000..6a6ee17cf --- /dev/null +++ b/build-locally.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash +# +# Build the CFEngine documentation locally in a Docker container. +# +# Self-contained: clones the sibling repos that the build expects +# (core, nova, enterprise, masterfiles, nt-docs) into ./tmp/ and runs +# the existing Docker-based pipeline against them, without requiring +# anything outside this directory. +# +# Override via env vars if needed: +# BRANCH branch name to build for (default: master) +# PACKAGE_JOB cf-remote or a buildcache job (default: cf-remote) +# PACKAGE_UPLOAD_DIRECTORY (default: n/a — unused with cf-remote) +# PACKAGE_BUILD (default: n/a — unused with cf-remote) +# LTS_VERSION (default: empty) +# DOCKER docker binary to use (default: docker) +# IMAGE_NAME tag for the build image (default: cfengine-docs-hugo) +# SKIP_PUBLISH=1 skip the _publish.sh step (just build) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +TMP_DIR="$SCRIPT_DIR/tmp" +CACHE_DIR="$TMP_DIR/cache" # persistent clones with .git +WORK_DIR="$TMP_DIR/work" # clean working copies (.git stripped) — what we mount +DOC_WORK="$WORK_DIR/documentation" +mkdir -p "$CACHE_DIR" "$WORK_DIR" + +BRANCH="${BRANCH:-master}" +PACKAGE_JOB="${PACKAGE_JOB:-cf-remote}" +PACKAGE_UPLOAD_DIRECTORY="${PACKAGE_UPLOAD_DIRECTORY:-n/a}" +PACKAGE_BUILD="${PACKAGE_BUILD:-n/a}" +LTS_VERSION="${LTS_VERSION:-}" +DOCKER="${DOCKER:-docker}" +IMAGE_NAME="${IMAGE_NAME:-cfengine-docs-hugo}" + +# repo_name url default_branch +REPOS=( + "core git@github.com:cfengine/core.git master" + "nova git@github.com:cfengine/nova.git master" + "enterprise git@github.com:cfengine/enterprise.git master" + "masterfiles git@github.com:cfengine/masterfiles.git master" + "nt-docs git@github.com:northerntechhq/nt-docs.git main" +) + +# 1. Clone (or update) the sibling repos under tmp/cache/, then export a +# clean working copy (no .git) to tmp/work/. We mount the .git-free +# copy because the container does `chmod -R` over each repo, and on +# macOS Docker bind mounts can't chmod git pack files written by the +# host user. +echo "==> Preparing sibling repos under $TMP_DIR" +for entry in "${REPOS[@]}"; do + # shellcheck disable=SC2086 + set -- $entry + name="$1"; url="$2"; default_branch="$3" + cache="$CACHE_DIR/$name" + work="$WORK_DIR/$name" + + if [ -d "$cache/.git" ]; then + echo " - $name: fetching latest" + git -C "$cache" fetch --quiet --tags origin + else + echo " - $name: cloning $url" + git clone --quiet "$url" "$cache" + fi + + if git -C "$cache" rev-parse --verify --quiet "origin/$BRANCH" >/dev/null; then + git -C "$cache" checkout --quiet -B "$BRANCH" "origin/$BRANCH" + else + echo " branch '$BRANCH' not found in $name; using '$default_branch'" + git -C "$cache" checkout --quiet -B "$default_branch" "origin/$default_branch" + fi + + # Export a clean snapshot for the container. Using `git archive` so + # we get exactly what's tracked, without .git or untracked junk. + rm -rf "$work" + mkdir -p "$work" + git -C "$cache" archive --format=tar HEAD | tar -x -C "$work" +done + +# 1b. Sync the documentation source itself into tmp/work/documentation. +# The build mutates files in place (sed on config.toml, cfdoc_preprocess.py +# rewriting markdown, etc.), so we must NOT bind-mount the user's checkout +# directly. Use rsync with --delete to keep the copy in sync (including +# uncommitted/untracked changes) without dragging tmp/ or .git into it. +echo "==> Syncing documentation source to $DOC_WORK" +mkdir -p "$DOC_WORK" +rsync -a --delete \ + --exclude='/tmp/' \ + --exclude='/.git/' \ + "$SCRIPT_DIR/" "$DOC_WORK/" + +# 2. Build the docker image (only if it's not already built). +if ! "$DOCKER" image inspect "$IMAGE_NAME" >/dev/null 2>&1; then + echo "==> Building docker image $IMAGE_NAME" + "$DOCKER" build --tag "$IMAGE_NAME" "$SCRIPT_DIR/generator/build" +else + echo "==> Reusing docker image $IMAGE_NAME (delete it to rebuild)" +fi + +# 3. Run the build inside the container. +# main.sh expects /nt/{documentation,core,nova,enterprise,masterfiles,nt-docs}. +# We bind-mount this checkout as /nt/documentation and each tmp/ as +# its sibling, so nothing outside this directory is touched. +echo "==> Running documentation build in container" +RUN_FLAGS=( + --rm + -v "$DOC_WORK:/nt/documentation" +) +for entry in "${REPOS[@]}"; do + # shellcheck disable=SC2086 + set -- $entry + RUN_FLAGS+=(-v "$WORK_DIR/$1:/nt/$1") +done + +"$DOCKER" run "${RUN_FLAGS[@]}" "$IMAGE_NAME" \ + bash -x documentation/generator/build/main.sh \ + "$BRANCH" "$PACKAGE_JOB" "$PACKAGE_UPLOAD_DIRECTORY" \ + "$PACKAGE_BUILD" "$LTS_VERSION" + +# 4. Optionally package the result (mirrors the Jenkins pipeline). +if [ -z "${SKIP_PUBLISH:-}" ]; then + echo "==> Packaging output" + "$DOCKER" run "${RUN_FLAGS[@]}" "$IMAGE_NAME" \ + bash -x documentation/generator/_scripts/_publish.sh "$BRANCH" +fi + +echo "==> Done. Generated site is in: $DOC_WORK/generator/_site" +echo " Tarballs (if packaged) are in: $DOC_WORK/output/" From 2d317326a2706cb72555f44081138ab83f9b66dd Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Thu, 21 May 2026 01:21:22 +0200 Subject: [PATCH 3/7] Fixed issue where autolinking tries to put autolinks in policy Co-authored-by: Claude Opus 4.7 (1M context) Signed-off-by: Ole Herman Schumacher Elgesem --- generator/_scripts/cfdoc_linkresolver.py | 21 ++++++--- .../_scripts/cfdoc_references_resolver.py | 44 +++++++++++++++---- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/generator/_scripts/cfdoc_linkresolver.py b/generator/_scripts/cfdoc_linkresolver.py index bf90c8f64..5b7785a87 100644 --- a/generator/_scripts/cfdoc_linkresolver.py +++ b/generator/_scripts/cfdoc_linkresolver.py @@ -237,12 +237,21 @@ def applyLinkMap(file_name, config): for markdown_line in markdown_lines: config["context_current_line_number"] += 1 # we ignore everything in code blocks - if previous_empty or in_pre: - if markdown_line.lstrip()[:3] == "```": - in_pre = not in_pre - if markdown_line[:4] == " ": - new_lines.append(markdown_line) - continue + # triple-backtick fences must be detected on every line, not only after + # a blank line, otherwise content inside the block gets autolinked + if markdown_line.lstrip()[:3] == "```": + in_pre = not in_pre + new_lines.append(markdown_line) + previous_empty = False + continue + if in_pre: + new_lines.append(markdown_line) + previous_empty = markdown_line.lstrip() == "" + continue + if previous_empty and markdown_line[:4] == " ": + new_lines.append(markdown_line) + previous_empty = False + continue # don't link to the current section if markdown_line.find("title:") == 0: diff --git a/generator/_scripts/cfdoc_references_resolver.py b/generator/_scripts/cfdoc_references_resolver.py index 64a36810d..f511a5aaa 100644 --- a/generator/_scripts/cfdoc_references_resolver.py +++ b/generator/_scripts/cfdoc_references_resolver.py @@ -40,15 +40,34 @@ def process(file_path, references, missing): Any reference that cannot be resolved is appended to `missing` as a (file_path, ref) tuple so the caller can fail the build. + + Code content is skipped so that things that look like reference links but + aren't (CFEngine array notation `a[b][c]`, sample output `[1][0]`, etc.) + don't get treated as broken `[text][ref]` links: + * Triple-backtick fenced code blocks — common when snippets are pulled + in from the core repo via macros. + * Inline single-backtick code spans on a single line. + + Function-name autolinking (``foo()``) is the inverse: it deliberately + targets backtick-quoted text, so it runs on every non-fenced line. """ with open(file_path, "r", encoding="utf-8") as f: - content = f.read() + lines = f.readlines() # Pattern to match reference links: [`text`][reference] - pattern = r"\[(.*?)\]\[(.*?)\]" + pattern = re.compile(r"\[(.*?)\]\[(.*?)\]") + # finds functions except ones already processed inside [] + functions_pattern = re.compile(r"(? Date: Wed, 17 Jun 2026 13:50:14 +0200 Subject: [PATCH 4/7] Added _no_links.md to explicitly skip autolinking for some exceptions Co-authored-by: Claude Opus 4.8 Signed-off-by: Ole Herman Schumacher Elgesem --- generator/_no_links.md | 12 +++++ .../_scripts/cfdoc_references_resolver.py | 44 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 generator/_no_links.md diff --git a/generator/_no_links.md b/generator/_no_links.md new file mode 100644 index 000000000..5058beb58 --- /dev/null +++ b/generator/_no_links.md @@ -0,0 +1,12 @@ + + +validate_promise() +evaluate_promise() diff --git a/generator/_scripts/cfdoc_references_resolver.py b/generator/_scripts/cfdoc_references_resolver.py index f511a5aaa..7d125dc47 100644 --- a/generator/_scripts/cfdoc_references_resolver.py +++ b/generator/_scripts/cfdoc_references_resolver.py @@ -35,7 +35,43 @@ def load_references(references_file): return references -def process(file_path, references, missing): +def load_no_links(no_links_file): + """Parse the _no_links.md file and return a set of function names that + should NOT be autolinked. + + Each non-empty, non-comment line is a name like ``foo()`` or ``foo``; the + trailing ``()`` is optional. HTML comment blocks (````, possibly + spanning multiple lines) are ignored. Names are stored lowercased for + case-insensitive matching against autolink candidates. + """ + no_links = set() + + path = os.path.join(os.environ.get("WRKDIR"), no_links_file) + if not os.path.exists(path): + sys.stderr.write( + f"Warning: No-links file {path} not found. All function names will be autolinked.\n" + ) + return no_links + + try: + with open(path, "r", encoding="utf-8") as file: + content = file.read() + except Exception as e: + sys.stderr.write(f"Error reading no-links file {path}: {str(e)}\n") + return no_links + + # Drop HTML comment blocks (including multi-line ones) before parsing names. + content = re.sub(r"", "", content, flags=re.DOTALL) + for line in content.splitlines(): + line = line.strip() + if not line: + continue + no_links.add(line.removesuffix("()").lower()) + + return no_links + + +def process(file_path, references, no_links, missing): """Process a markdown file and replace reference links with direct links. Any reference that cannot be resolved is appended to `missing` as a @@ -89,6 +125,9 @@ def replace_function_link(match): ref = match.group(1) text = f"{ref}()" ref_lower = ref.lower() + if ref_lower in no_links: + # Explicitly opted out of autolinking; leave as plain text. + return match.group(0) if ref_lower in references: url, title = references[ref_lower] if title: @@ -126,10 +165,11 @@ def run(config): """ markdown_files = config["markdown_files"] references = load_references("documentation/generator/_references.md") + no_links = load_no_links("documentation/generator/_no_links.md") missing = [] for file in markdown_files: - process(file, references, missing) + process(file, references, no_links, missing) if missing: sys.stderr.write( From 797458b8e9dfcb39056c2dae83122861c96128ef Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Wed, 17 Jun 2026 16:36:25 +0200 Subject: [PATCH 5/7] Fixed issue with cfdoc_log.markdown being included in the build This file was growing with more and more content which caused errors during build, like broken links. Co-authored-by: Claude Opus 4.8 Signed-off-by: Ole Herman Schumacher Elgesem --- build-locally.sh | 10 ++++++++++ generator/_scripts/cfdoc_references_resolver.py | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/build-locally.sh b/build-locally.sh index 6a6ee17cf..19c24a2d6 100755 --- a/build-locally.sh +++ b/build-locally.sh @@ -85,12 +85,22 @@ done # rewriting markdown, etc.), so we must NOT bind-mount the user's checkout # directly. Use rsync with --delete to keep the copy in sync (including # uncommitted/untracked changes) without dragging tmp/ or .git into it. +# cfdoc_log.markdown is a build artifact written into content/ by cfdoc_qa.py +# (it's gitignored). If a previous run left it behind, the link checker +# re-parses its log entries — which themselves contain literal +# [foo#foo][foo#foo] markdown — and reports hundreds of bogus "unresolved +# reference" errors. We must clear it from both sides: --exclude keeps the +# host's copy from being synced in, and the explicit rm removes any copy a +# previous in-container build wrote into the work tree (rsync --delete will +# NOT remove an --exclude'd path, so the exclude alone is not enough). echo "==> Syncing documentation source to $DOC_WORK" mkdir -p "$DOC_WORK" rsync -a --delete \ --exclude='/tmp/' \ --exclude='/.git/' \ + --exclude='/content/cfdoc_log.markdown' \ "$SCRIPT_DIR/" "$DOC_WORK/" +rm -f "$DOC_WORK/content/cfdoc_log.markdown" # 2. Build the docker image (only if it's not already built). if ! "$DOCKER" image inspect "$IMAGE_NAME" >/dev/null 2>&1; then diff --git a/generator/_scripts/cfdoc_references_resolver.py b/generator/_scripts/cfdoc_references_resolver.py index 7d125dc47..2531c5b76 100644 --- a/generator/_scripts/cfdoc_references_resolver.py +++ b/generator/_scripts/cfdoc_references_resolver.py @@ -169,6 +169,12 @@ def run(config): missing = [] for file in markdown_files: + # cfdoc_log.markdown is the QA log written by cfdoc_qa.py, not real + # documentation. Its entries quote link warnings using literal + # [foo#foo][foo#foo] markdown, so linting it produces hundreds of bogus + # "unresolved reference" errors. Never treat it as a source file. + if os.path.basename(file) == "cfdoc_log.markdown": + continue process(file, references, no_links, missing) if missing: From 01e4f59e2e1b64d93d4a37cec8291f6e7440f65f Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Wed, 17 Jun 2026 13:53:00 +0200 Subject: [PATCH 6/7] Fixed link for custom promise types Co-authored-by: Claude Opus 4.8 Signed-off-by: Ole Herman Schumacher Elgesem --- .../promise-type-module-development.markdown | 2 +- .../general-installation/_index.markdown | 2 +- .../local-virtual-machine.markdown | 2 +- content/overview/_index.markdown | 2 +- .../reference/promise-types/services.markdown | 2 +- .../resources/faq/bootstrap-failed.markdown | 4 +- .../faq/integrate-custom-policy.markdown | 4 +- ...y-are-files-not-being-distributed.markdown | 2 +- generator/_no_links.md | 3 ++ generator/_scripts/cfdoc_linkresolver.py | 18 +++++++-- .../_scripts/cfdoc_references_resolver.py | 37 ++++++++++++++++--- 11 files changed, 58 insertions(+), 20 deletions(-) diff --git a/content/examples/tutorials/promise-type-module-development.markdown b/content/examples/tutorials/promise-type-module-development.markdown index 9b6cb5558..eb92b007f 100644 --- a/content/examples/tutorials/promise-type-module-development.markdown +++ b/content/examples/tutorials/promise-type-module-development.markdown @@ -10,7 +10,7 @@ aliases: Promise type modules are easy to write. In this tutorial, we will focus on implementing a new promise type in Python, with the provided CFEngine library, since this is the easiest and recommended way. -If you are interested in how modules are implemented, or how you could do it in another programming language, see the [complete documentation][custom]. +If you are interested in how modules are implemented, or how you could do it in another programming language, see the [complete documentation][promise-type-custom]. In short, you need to implement 2 functions: `validate_promise()` and `evaluate_promise()`. _Validation_ should check that the correct attributes are used, and any other constraints you may want to enforce, to determine whether a promise is valid or invalid. diff --git a/content/getting-started/01-installing-cfengine/general-installation/_index.markdown b/content/getting-started/01-installing-cfengine/general-installation/_index.markdown index 9a56fa7c3..26db4e8ce 100644 --- a/content/getting-started/01-installing-cfengine/general-installation/_index.markdown +++ b/content/getting-started/01-installing-cfengine/general-installation/_index.markdown @@ -119,7 +119,7 @@ Edit `/etc/hosts` and add an entry for the IP address and hostname of the server ### CFEngine Enterprise post-installation setup -See: [What steps should I take after installing CFEngine Enterprise?][FAQ#What steps should I take after installing CFEngine Enterprise] +See: [What steps should I take after installing CFEngine Enterprise?][Enterprise reporting database#What steps should I take after installing CFEngine Enterprise?] ## More detailed installation guides diff --git a/content/getting-started/01-installing-cfengine/local-virtual-machine.markdown b/content/getting-started/01-installing-cfengine/local-virtual-machine.markdown index 98f5938eb..ee5256a50 100644 --- a/content/getting-started/01-installing-cfengine/local-virtual-machine.markdown +++ b/content/getting-started/01-installing-cfengine/local-virtual-machine.markdown @@ -121,4 +121,4 @@ vagrant destroy hub Now that you have a Linux VM ready, go back to the main tutorial to install CFEngine: -[Installation][Installation] +[Installation][Installing CFEngine] diff --git a/content/overview/_index.markdown b/content/overview/_index.markdown index 55f97a381..38341195c 100644 --- a/content/overview/_index.markdown +++ b/content/overview/_index.markdown @@ -6,7 +6,7 @@ aliases: - "/overview.html" --- -CFEngine is a distributed system for managing and monitoring computers across an IT network. Machines on the network that have CFEngine installed, and have registered themselves with a policy server (see [Installation][Installation]), will each be running a set of CFEngine component applications that manage and interpret a textual representation of your desired state for the system, referred to as policy. Policy files themselves contain sets of instructions to ensure machines on the network are in full compliance with a defined state. At the atomic level are sets, or _bundles_, of what are known in the CFEngine world as [Promises][Promises]. _Promises_ are at the heart of Promise Theory, which is in turn what CFEngine is all about. +CFEngine is a distributed system for managing and monitoring computers across an IT network. Machines on the network that have CFEngine installed, and have registered themselves with a policy server (see [Installation][Installing CFEngine]), will each be running a set of CFEngine component applications that manage and interpret a textual representation of your desired state for the system, referred to as policy. Policy files themselves contain sets of instructions to ensure machines on the network are in full compliance with a defined state. At the atomic level are sets, or _bundles_, of what are known in the CFEngine world as [Promises][Promises]. _Promises_ are at the heart of Promise Theory, which is in turn what CFEngine is all about. ## Policy language and compliance diff --git a/content/reference/promise-types/services.markdown b/content/reference/promise-types/services.markdown index 7ed5dd152..79b4598b1 100644 --- a/content/reference/promise-types/services.markdown +++ b/content/reference/promise-types/services.markdown @@ -249,7 +249,7 @@ bundle agent my_custom_service_method_deb( service_identifier, desired_service_s } ``` -**See also:** [generic standard_services][Services Bodies and Bundles#standard_services] +**See also:** [generic standard_services][lib/services.cf] **History:** diff --git a/content/resources/faq/bootstrap-failed.markdown b/content/resources/faq/bootstrap-failed.markdown index abb32d05a..eefa75aba 100644 --- a/content/resources/faq/bootstrap-failed.markdown +++ b/content/resources/faq/bootstrap-failed.markdown @@ -136,7 +136,7 @@ In order for a host to communicate it must be within an IP range that is allowed - `def.acl` in the Masterfiles Policy Framework is included in this list by default. -See also: [`def.acl`][Masterfiles Policy Framework#acl], [`def.trustkeysfrom`][Masterfiles Policy Framework#trustkeysfrom] +See also: [`def.acl`][Masterfiles Policy Framework#acl], [`def.trustkeysfrom`][Masterfiles Policy Framework#Automatic bootstrap - Trusting keys from new hosts with trustkeysfrom] ### `trustkeysfrom` in `body server control` @@ -149,4 +149,4 @@ This defines networks from which a host will automatically trust hosts. If you d - `verbose: 192.168.56.4> Trying old style '/var/cfengine/ppkeys/root-192.168.56.4.pub'` - `verbose: 192.168.56.4> Received key 'SHA=85f8a23d6738599e03951e6930e661bcd9bb3ae12f32486c9795cc9baa7d5b4e' not found in ppkeys` -See also: [`def.acl`][Masterfiles Policy Framework#acl], [`def.trustkeysfrom`][Masterfiles Policy Framework#trustkeysfrom] +See also: [`def.acl`][Masterfiles Policy Framework#acl], [`def.trustkeysfrom`][Masterfiles Policy Framework#Automatic bootstrap - Trusting keys from new hosts with trustkeysfrom] diff --git a/content/resources/faq/integrate-custom-policy.markdown b/content/resources/faq/integrate-custom-policy.markdown index cfad1716a..e102076d5 100644 --- a/content/resources/faq/integrate-custom-policy.markdown +++ b/content/resources/faq/integrate-custom-policy.markdown @@ -22,7 +22,7 @@ The _autorun_ feature in the Masterfiles Policy Framework automatically adds policy files found in `services/autorun` to inputs and executes bundles tagged with _autorun_ as methods type promises in lexical order. -**See also:** [`services_autorun` in the Masterfiles Policy Framework][Masterfiles Policy Framework#services\_autorun] +**See also:** [`services_autorun` in the Masterfiles Policy Framework][mpf-services-autorun] ## Using augments @@ -51,7 +51,7 @@ To extend inputs in the update policy define `update_inputs`. } ``` -**See also:** [Augments][Augments], [Extend inputs for update policy in the Masterfiles Policy Framework][Masterfiles Policy Framework#Append to inputs used by update policy] +**See also:** [Augments][Augments], [Extend inputs for update policy in the Masterfiles Policy Framework][Append to inputs used by update policy] ## Using body file control diff --git a/content/resources/faq/why-are-files-not-being-distributed.markdown b/content/resources/faq/why-are-files-not-being-distributed.markdown index 6aca0d9f6..bea543f0b 100644 --- a/content/resources/faq/why-are-files-not-being-distributed.markdown +++ b/content/resources/faq/why-are-files-not-being-distributed.markdown @@ -13,4 +13,4 @@ However not all files are considered for update in the default update policy. The default update policy in the MPF only copies files matching a list of -regular expressions defined in [update_def.input_name_patterns][Masterfiles Policy Framework#files considered for copy during policy updates] +regular expressions defined in [update_def.input_name_patterns][Masterfiles Policy Framework#Extend files considered for copy during policy updates] diff --git a/generator/_no_links.md b/generator/_no_links.md index 5058beb58..47dcb4f57 100644 --- a/generator/_no_links.md +++ b/generator/_no_links.md @@ -10,3 +10,6 @@ validate_promise() evaluate_promise() +PolicyResolve() +BodyToJson() +BundleToJson() diff --git a/generator/_scripts/cfdoc_linkresolver.py b/generator/_scripts/cfdoc_linkresolver.py index 5b7785a87..5b8c2e45e 100644 --- a/generator/_scripts/cfdoc_linkresolver.py +++ b/generator/_scripts/cfdoc_linkresolver.py @@ -237,15 +237,25 @@ def applyLinkMap(file_name, config): for markdown_line in markdown_lines: config["context_current_line_number"] += 1 # we ignore everything in code blocks - # triple-backtick fences must be detected on every line, not only after - # a blank line, otherwise content inside the block gets autolinked - if markdown_line.lstrip()[:3] == "```": - in_pre = not in_pre + # Track fenced code blocks the way CommonMark/Hugo do, so we don't + # autolink inside code and don't lose state on macro-injected examples: + # * a line is a fence only if it starts with ``` and has no further + # ``` on the same line (inline "```x```" is not a fence); + # * an open block closes only on a *bare* ``` (no info string), so a + # "```cf3" line appearing inside an open block counts as content. + # A naive toggle miscounts inner fences and flips the in/out-of-code + # state for the rest of the file. + stripped = markdown_line.lstrip() + is_fence = stripped.startswith("```") and "```" not in stripped[3:] + if is_fence and not in_pre: + in_pre = True new_lines.append(markdown_line) previous_empty = False continue if in_pre: new_lines.append(markdown_line) + if is_fence and stripped.strip().strip("`") == "": + in_pre = False previous_empty = markdown_line.lstrip() == "" continue if previous_empty and markdown_line[:4] == " ": diff --git a/generator/_scripts/cfdoc_references_resolver.py b/generator/_scripts/cfdoc_references_resolver.py index 2531c5b76..4d44a5cb1 100644 --- a/generator/_scripts/cfdoc_references_resolver.py +++ b/generator/_scripts/cfdoc_references_resolver.py @@ -91,10 +91,17 @@ def process(file_path, references, no_links, missing): with open(file_path, "r", encoding="utf-8") as f: lines = f.readlines() - # Pattern to match reference links: [`text`][reference] - pattern = re.compile(r"\[(.*?)\]\[(.*?)\]") - # finds functions except ones already processed inside [] - functions_pattern = re.compile(r"(? Date: Wed, 17 Jun 2026 21:08:53 +0200 Subject: [PATCH 7/7] Fixed build errors Co-authored-by: Claude Opus 4.8 Signed-off-by: Ole Herman Schumacher Elgesem --- .../adjusting-schedules.markdown | 4 ++-- generator/_references.md | 1 + generator/_scripts/cfdoc_macros.py | 22 +++++++++++++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/content/web-ui/hub_administration/adjusting-schedules.markdown b/content/web-ui/hub_administration/adjusting-schedules.markdown index 4664ac8a5..752dee5b1 100644 --- a/content/web-ui/hub_administration/adjusting-schedules.markdown +++ b/content/web-ui/hub_administration/adjusting-schedules.markdown @@ -8,8 +8,8 @@ aliases: ## Set cf-execd agent execution schedule By default `cf-execd` is configured to run `cf-agent` every 5 minutes. This can -be adjusted by tuning the [schedule][cf-execd#schedule] in `body executor -control`. In the [Masterfiles Policy Framework][Masterfiles Policy Framework] body +be adjusted by tuning the [schedule][cf-execd#schedule] in +`body executor control`. In the [Masterfiles Policy Framework][Masterfiles Policy Framework] body executor control can be found in `controls/cf_execd.cf` ## Set cf-hub hub_schedule diff --git a/generator/_references.md b/generator/_references.md index 77f8abddd..bca1c63ce 100644 --- a/generator/_references.md +++ b/generator/_references.md @@ -39,6 +39,7 @@ [sys.uqhost]: reference-special-variables-sys.html#sys-uqhost [sys.policy_hub]: reference-special-variables-sys.html#sys-policy_hub [seed_cp]: reference-masterfiles-policy-framework-lib-files.html#seed_cp +[Masterfiles Policy Framework]: reference-masterfiles-policy-framework.html [Append to inputs used by main policy]: reference-masterfiles-policy-framework.html#append-to-inputs-used-by-main-policy [mpf_extra_autorun_inputs]: reference-masterfiles-policy-framework.html#additional-automatically-loaded-inputs [Append to inputs used by update policy]: reference-masterfiles-policy-framework.html#append-to-inputs-used-by-update-policy diff --git a/generator/_scripts/cfdoc_macros.py b/generator/_scripts/cfdoc_macros.py index 8fc407a2d..cc7012239 100644 --- a/generator/_scripts/cfdoc_macros.py +++ b/generator/_scripts/cfdoc_macros.py @@ -620,11 +620,25 @@ def prune_include_lines(markdown_lines, filename): # unless included example starts with an explicit code block, start a (CFEngine-brushed) block if markdown_lines[0].find("```") != 0: markdown_lines.insert(0, "\n```%s\n" % brush) - # if example ended with documentation, prune trailing code, else terminate block - if markdown_lines[-1] != ("\n```%s\n" % brush): - markdown_lines.append("```\n") - else: + if markdown_lines[-1] == ("\n```%s\n" % brush): + # snippet ended by opening a fresh (empty) code block; drop it del markdown_lines[-1] + else: + # Close the block only if the snippet actually ends *inside* one. + # An example that ends with documentation (#@ ...) has already + # closed its last code block, so appending another ``` would leave + # an unmatched fence that swallows all the content following the + # include (e.g. hiding real reference links from the resolvers). + in_code = False + for ml in markdown_lines: + stripped = ml.lstrip() + if stripped.startswith("```") and "```" not in stripped[3:]: + if not in_code: + in_code = True + elif stripped.strip().strip("`") == "": + in_code = False + if in_code: + markdown_lines.append("```\n") return markdown_lines