OpsMop Logo

Facts

OpsMop has several ways to learn about the running system. These are collectively known as ‘Facts’.

Users can write their own ‘Facts’ classes, or use one of the many included ones:

  • Platform
  • UserFacts
  • FileTests

All facts can be used in Conditions, Hooks as well as Templates - in fact, they are just Python instances with functions, so you can use them anywhere.

Here’s a basic example from Platform:

def set_resources(self):
    darwin = (Platform.system() == "Darwin")
    return Resources(
        Echo("My platform is {{ Plaform.system() }}"),
        Echo("This is OS X.", when=darwin)
    )

Or as we showcased in Hooks, it’s also easy to attach a conditional to a whole Role, or even a Policy. For most use cases, we definitely prefer this format:

Platform

Platform Facts tell you something about the OS or Machine you are running on.

More platform facts will be added frequently, and also make easy first pull requests. See Development Guide if you are interested in adding something.

Function Description
default_package_provider() used internally by the Package type
default_service_provider() used internally by the Package type
release() from python’s platform module
system() from python’s platform module
version() from python’s platform module
os_distribution() the distribution as reported by the OS, ex: ‘CentOS Linux’
os_version_number() ex: 7.2
os_version_string() ex: “7.2.1234”

Example:

class LinuxSetup(Role):

    def should_process_when(self):
        return (Platform.system() != "Darwin")

    def set_resources(self):
        # ...

Example:

class Foo(Role):


    def set_resources(self):
        darwin = (Platform.system() == "Darwin")
        return Resources(
            # ...
            Echo("this only runs on OS X", when=darwin)
            #..
        )

Example (Jinja2 template):

class Foo(Role):

    def set_resources(self):
        return Resources(
            File("/etc/foo.cfg", from_template="templates/foo.cfg.j2"),
        )

Here is a Jinja2 template example:

# This file is programatically generated by OpsMop: templates/foo.confg.j2

example_config_file=made_up
parameters=imaginary

{% if Platform.system() == 'Darwin' %}
asdf=1234
{% else %}
asdf=5678
{% endif %}

jklm=101010101010

Chaos

Chaos facts are intended for use in Chaos Engineering. They return random values, allowing you to randomly perform steps, or sometimes even randomly misconfigure your systems.

While we’re not responsible for anything you do with OpsMop, we would like to remind you now that with great power comes great responsibility.

Function Description
choice(a,b,c,d,e) randomly returns one of the parameters
random() returns a float between 0 and 1

Example:

class FooRole(Role):

    def should_process_when(self):
        return (Chaos.random() < 0.25)

    def set_resources(self):
        # ...

Or in a Jinja2 template:

{% if Chaos.random() < 0.33 %}
blocksize=4091 # misconfigured! whooohoo, sneaky
{% else %}
blocksize=4092
{% endif %}

FileTests

FileTest facts let you ask questions about files and directories.

These are functions that take one or more path names and return booleans, strings, or integers.

Function Description
exists(f) Does the path exist? - True/False
executable(f) Is the path executable? - True/False
is_file(f) Is the path a file? - True/False
is_directory(f) Is the path a directory? - True/False
mode(f) Return the numeric mode of the path, ex: 0o770
user(f) Return the owner user of the path
group(f) Return the owner group of the path
checksum(f) Return the sha1sum of the path
checksum_string(s) Return the sha1sum of a string (bonus)

If you are ok with an early file check, here is a shell command that only runs if a file does not exist. However, it is important to note this runs as soon as the object is constructed, which might be wrong:

def set_resources(self):
    return Resources(
        Shell("/bin/foo", when=(not FileTests.exists("/blarg/foo")))
    )

Here is an improved late binding example:

def set_resources(self):
    exists = Eval('not FileTests.exists("/blarg/foo")')
    resources = Resources(
        Shell("/bin/foo", when=not_exists),
    )

And of course technically you can still do these checks within Jinja2, but it’s a little hard to think of a use case for that:

{% if FileTests.exists("/blarg/foo") %}
flag=True
{% endif %}

Note

Many older configuration management systems have implemented existance tests specifically on a command or exec resource. In OpsMop, file existance (or absence, or status, or … anything) can gate the evaluation of any resource.

Note

Pretty much all of the FileTest facts take parameters, which means they can’t be debugged by DebugFacts Module. Using them in a template or Echo statement can be a good way to debug if you need to.

UserFacts

UserFacts are easily user-customizable facts that you can use WITHOUT making new Python classes. They are generated by scanning files in /etc/opsmop/facts.d/.

If a file is not executable, the file will be interpreted as a dictionary in either JSON or YAML format.

If the file is executable, the file will be evaluated, and the output will be evaluated as a dictionary in either JSON or YAML format. The program, in this case, can be written in any language at all.

For a full demo of this, see user_facts.py.

UserFacts are usually calculated only once per run, for efficiency. The example includes an invalidate() call if you want to learn how to re-evaluate the facts.

To test UserFacts, see Debuging Facts.

Access looks like this:

In Python, nested array and dictionary values look normal:

In Jinja2, you can of course also use ‘.’:

UserFacts.variable_name.sub_element[2]

Debuging Facts

For modules other than FileTest, here’s a quick way to show Fact values:

python -m opsmop.facts.platform
python -m opsmop.facts.user_facts

Alternatively see DebugFacts Module to show the same things while running a Policy.

Custom Facts

If you don’t want to use UserFacts, you can also write your own fact classes. This is a little more involved, but still easy.

You will need to extend opsmop.core.policy.Policy to inject the new facts into the Template namespace IF you want to surface those Fact classes in the Jinja2 template environment. Then, in your policy files, always use your new base class, like AcmeCorpPolicy, instead of the Policy object that ships with OpsMop.

You will also want to make sure you import the Facts so you can use them in conditionals in addition to templates.

See also Development Guide.

Want To Add New Facts?

Contribution of new facts (particularly OS/hardware related facts) to the main fact code is quite welcome. See Development Guide. Thank you!