Introducing cfbs 4.4.0 and the analyze command

Posted by Nick Anderson
April 8, 2025

The latest release of cfbs (4.4.0 released April 4th, 2025) introduces the analyze command.

Last time I used this (Show notes: The agent is in - Episode 47 - Preview of cfbs analyze) I had installed it from a git clone, now I want to go back to regular install

command
pipx uninstall cfbs
pipx install cfbs
output
uninstalled cfbs! ✨ 🌟 ✨
  installed package cfbs 4.4.0, installed using Python 3.12.3
  These apps are now globally available
    - cfbs

Now, cfbs help should have our new cfbs analyze option:

command
cfbs help
output
usage: cfbs [-h] [--loglevel LOGLEVEL] [-M] [--version] [--force]
            [--non-interactive] [--index INDEX] [--check]
            [--checksum CHECKSUM] [--keep-order] [--git {yes,no}]
            [--git-user-name GIT_USER_NAME] [--git-user-email GIT_USER_EMAIL]
            [--git-commit-message GIT_COMMIT_MESSAGE] [--ignore-versions-json]
            [--omit-download] [--check-against-git] [--from MINIMUM_VERSION]
            [--to-json [TO_JSON]] [--reference-version REFERENCE_VERSION]
            [--masterfiles-dir MASTERFILES_DIR]
            [--ignored-path-components [IGNORED_PATH_COMPONENTS ...]]
            [--offline] [--masterfiles MASTERFILES]
            [cmd] [args ...]

CFEngine Build System.

positional arguments:
  cmd                   The command to perform (pretty, init, status, search,
                        add, remove, clean, update, validate, download, build,
                        install, help, info, show, analyse, analyze, input,
                        set-input, get-input, generate-release-information)
  args                  Command arguments

options:
  -h, --help            show this help message and exit
  --loglevel LOGLEVEL, -l LOGLEVEL
                        Set log level for more/less detailed output
  -M, --manual          Print manual page
  --version, -V         Print version number
  --force               Force rebuild / redownload
  --non-interactive     Don't prompt, use defaults (only for testing)
  --index INDEX         Specify alternate index
  --check               Check if file(s) would be reformatted
  --checksum CHECKSUM   Expected checksum of the downloaded file
  --keep-order          Keep order of items in the JSON in 'cfbs pretty'
  --git {yes,no}        Override git option in cfbs.json
  --git-user-name GIT_USER_NAME
                        Specify git user name
  --git-user-email GIT_USER_EMAIL
                        Specify git user email
  --git-commit-message GIT_COMMIT_MESSAGE
                        Specify git commit message
  --ignore-versions-json
                        Ignore versions.json. Necessary in case of a custom
                        index or testing changes to the default index.
  --omit-download       Use existing masterfiles instead of downloading in
                        'cfbs generate-release-information'
  --check-against-git   Check whether masterfiles from cfengine.com and
                        github.com match in 'cfbs generate-release-
                        information'
  --from MINIMUM_VERSION
                        Specify minimum version in 'cfbs generate-release-
                        information'
  --to-json [TO_JSON]   Output 'cfbs analyze' results to a JSON file;
                        optionally specify the JSON's filename
  --reference-version REFERENCE_VERSION
                        Specify version to compare against for 'cfbs analyze'
  --masterfiles-dir MASTERFILES_DIR
                        If the path given to 'cfbs analyze' contains a
                        masterfiles subdirectory, specify the subdirectory's
                        name
  --ignored-path-components [IGNORED_PATH_COMPONENTS ...]
                        Specify path components which should be ignored during
                        'cfbs analyze' (the components should be passed
                        separately, delimited by spaces)
  --offline             Do not connect to the Internet to download the latest
                        version of MPF release information during 'cfbs
                        analyze'
  --masterfiles MASTERFILES
                        Add masterfiles on cfbs init choose between

Let’s grab oldest version of the Masterfiles Policy Framework that cf-remote knows about and test it out.

command
OLDEST_MPF_IN_CFREMOTE="$(cf-remote list | head -n1 | awk '{print $NF}')"
URL_OLDEST_MPF_IN_CFREMOTE="$(cf-remote --version ${OLDEST_MPF_IN_CFREMOTE} list masterfiles | tail -n1)"
TARBALL_FILENAME_OLDEST_MPF_IN_CFREMOTE="$(basename ${URL_OLDEST_MPF_IN_CFREMOTE})"
cf-remote --version 3.21.0 download masterfiles --output-dir .
tar --no-same-permissions --no-overwrite-dir -zxf ${TARBALL_FILENAME_OLDEST_MPF_IN_CFREMOTE} 2> /dev/null
mv masterfiles "masterfiles-${OLDEST_MPF_IN_CFREMOTE}"
output
Available releases: master, 3.25.0, 3.24.x, 3.24.1, 3.24.0, 3.21.x, 3.21.6, 3.21.5, 3.21.4, 3.21.3, 3.21.2, 3.21.1, 3.21.0
Using 3.21.0 LTS:
Downloading package: '/home/nickanderson/.cfengine/cf-remote/packages/cfengine-masterfiles-3.21.0-1.pkg.tar.gz'
Copied to '/tmp/cfengine-masterfiles-3.21.0-1.pkg.tar.gz' (Checksum OK).

If we analyze it:

command
cfbs analyze "./masterfiles-${OLDEST_MPF_IN_CFREMOTE}"

We can see that it appears to be completely un-modified, as expected.

output
Policy set path: ./masterfiles-3.21.0

Reference version: 3.21.0

No files are missing from the version.
No files of the version are modified.
No files are from a different version.
No files are not from any version.

Now, let’s make some representative modifications that are typically found in policy sets:

Let’s remove one of the vendored files:

command
# Trigger Files missing from the version:
rm -f "./masterfiles-${OLDEST_MPF_IN_CFREMOTE}/services/autorun/hello.cf"

Let’s customize a vendored policy file:

command
# Trigger Files from the version but with modifications:
echo 'bundle agent mpf_main
{
  reports:
    "Customized services/main.cf";
}
bundle agent hello_world
{
  reports:
    "Hello World!";
}' > "./masterfiles-${OLDEST_MPF_IN_CFREMOTE}/services/main.cf"

Let’s introduce a new file:

command
# Trigger Files not from any version (with both custom content and path):
echo '{
 "variables": {
   "default:def.bundlesequence_end": [ "hello_world" ]
 }
}' > "./masterfiles-${OLDEST_MPF_IN_CFREMOTE}/def.json"

And, let’s introduce a file (but with different content) that would have been present in an older release of the MPF:

command
# Files from a different version, with modifications:
mkdir -p  "./masterfiles-${OLDEST_MPF_IN_CFREMOTE}/lib/3.7/"
echo "# Oops, we neglected to clean up this file from an old MPF version.  " > "./masterfiles-${OLDEST_MPF_IN_CFREMOTE}/lib/3.7/services.cf"

Now let’s analyze it:

command
cfbs analyze "./masterfiles-${OLDEST_MPF_IN_CFREMOTE}"

Looking at the result we see the files indicated in the appropriate section. For lib/3.7/services.cf notice that 4 different versions of the MPF were known to contain this file (‘3.7.8’, ‘3.7.7’, ‘3.7.6’, ‘3.7.5’), and it highlights the fact that the file still differed from what was included with those releases.

output
Policy set path: ./masterfiles-3.21.0

Reference version: 3.21.0

Files missing from the version:
└── services/autorun/hello.cf
Files from the version but with modifications:
└── services/main.cf
Files from a different version, with modifications:
└── ('lib/3.7/services.cf', ['3.7.8', '3.7.7', '3.7.6', '3.7.5'])
Files not from any version (with both custom content and path):
└── def.json

We aim to add additional functionality to this tooling, but we would love to hear your feedback on GitHub discussions or our mailing list.