progfigsite inventory module#

Required attributes and function calls

sitewrapper.set_progfigsite_by_module_name(site_name)

You must call this function to tell progfiguration core the name of the package. This must happen before any progfiguration core functions are called. See also progfiguration.sitewrapper.set_progfigsite_by_module_name().

hoststore

An implementation of the progfiguration.inventory.invstores.HostStore protocol. Progfiguration core ships with progfiguration.sitehelpers.memhosts.MemoryHostStore which can be instantiated directly, or via progfiguration.sitehelpers.invconf.hosts_conf() and a configuration file. Sites are free to implement their own alternative.

secretstore

An implementation of the progfiguration.inventory.invstores.SecretStore protocol. Progfiguration core ships with progfiguration.sitehelpers.agesecrets.AgeSecretStore, which can be instantiated directly, or via progfiguration.sitehelpers.invconf.secrets_conf() and a configuration file. Sites are free to implement their own alternative.

mint_version()

A function that takes no arguments and returns a valid pip version number.

This function should return a new version number, suitable for the next build of the pacakge. (This is different from the get_version() function in progfigsite root members, which returns the current version number.)

Progfiguration core ships with progfiguration.sitehelpers.siteversion.mint_version_factory_from_epoch(), which will return a function that uses the epoch time in the version number. Some implementations might prefer to implement their own mint_version(), perhaps to pull the version number from an environment variable injected by a CI service, etc.

progfigsite inventory.conf File#

An inventory.conf file is not technically part of progfiguration core, but it can be used with progfiguration.sitehelpers.invconf.hosts_conf() and progfiguration.sitehelpers.invconf.secrets_conf() to create a host store and secrets store respectively.

Here’s an example from example_site:

[secrets]
# The controller has an age key which can be used to decrypt any secret.
# When this file is not present, progfiguration will still work,
# but activities that require the secret key will throw errors.
controller_age_path = /path/to/controller.age

# We must hard code the public key in this file as well,
# so that it is available even when we aren't running on the controller.
controller_age_pub = paste the public key here

# The default/fallback location for the age key on each node.
node_fallback_age_path = /path/to/node.age

####
# Assign group membership
# You can specify more than one group by separating items with newlines or spaces
[groups]
group1 = node1

# group2 = node1 node2 node3

####
# Assign each node to a function.
# WARNING: Any node not listed here will not be visible to progfiguration,
# meaning that it cannot be deployed to, will not be in the 'universal' group, etc.
[node_function_map]
node1 = func1

####
# Set the list of roles for each function
# Roles can be separated by newlines or spaces
[function_role_map]
func1 = settz
# func2 = settz otherrole asdf etc

# TODO: fall back to this role so that we don't have to place new nodes in role_function_map
# and they will still come up.
default = settz

By itself, that inventory file does nothing, but when referenced in the site’s inventory module it generates the host and secret stores. Here is the inventory module from example_site:

"""Inventory module.

Must call :meth:`progfiguration.sitewrapper.set_progfigsite_by_module_name` in this module.

May use helper functions like
:meth:`progfiguration.sitehelpers.invconf.hosts_conf` and
:meth:`progfiguration.sitehelpers.invconf.secrets_conf`
to create the hoststore and secretstore.
"""

from progfiguration import sitewrapper
from progfiguration.sitehelpers import siteversion
from progfiguration.sitehelpers.invconf import hosts_conf, secrets_conf


# The progfigsite package must be set before calling anything else from progfiguration core.
sitewrapper.set_progfigsite_by_module_name("example_site")

hoststore = hosts_conf("inventory.conf")
"""The hoststore for the site

The :meth:`progfiguration.sitehelpers.invconf.hosts_conf` function
returns a :class:`progfiguration.sitehelpers.invconf.MemoryHostStore`.
The path we pass to it can be included in the site package
and found relative to the site package root.
"""

secretstore = secrets_conf("inventory.conf")
"""The secretstore for the site

The :meth:`progfiguration.sitehelpers.invconf.secrets_conf` function
returns a :class:`progfiguration.sitehelpers.invconf.AgeSecretStore`.
Note that we pass it the same config file -- it reads different sections.
"""


mint_version = siteversion.mint_version_factory_from_epoch(major=1, minor=0)
"""A function that generates a new version when it's called.

Set it from :meth:`sitehelpers.siteversion.mint_version_factory_from_epoch`,
which returns a ``mint_version()`` implementation
that returns f"{maj}.{min}.{epoch}".

Sites can write their own ``mint_version()`` if they wish,
perhaps pulling the version from a build number in a CI system.
"""

Inventory without inventory.conf#

As noted, you can instantiate the HostStore and SecretStore classes yourself, as in this example.

"""Inventory module.

Must call :meth:`progfiguration.sitewrapper.set_progfigsite_by_module_name` in this module.

May use helper functions like
:meth:`progfiguration.sitehelpers.invconf.hosts_conf` and
:meth:`progfiguration.sitehelpers.invconf.secrets_conf`
to create the hoststore and secretstore.
"""


from pathlib import Path

from progfiguration import sitewrapper
from progfiguration.sitehelpers.agesecrets import AgeSecretStore
from progfiguration.sitehelpers.memhosts import MemoryHostStore


# The progfigsite package must be set before calling anything else from progfiguration core.
sitewrapper.set_progfigsite_by_module_name("nnss_progfigsite")

# Use progfiguration core to create an inventory
hoststore = MemoryHostStore(
    groups={"group1": ["node1"]},
    node_function_map={"node1": "func1"},
    function_role_map={
        "func1": ["settz"],
        "default": ["settz"],
    },
)
"""The site's inventory"""

secretstore = AgeSecretStore(
    # This pubkey matches the private key we keep in the package directory.
    controller_age_pubkey="age1dmxq0tws40d8npseun9azpq65smpyd2kqfyj0ytlk6m7trn7nsxqnsrag2",
    decryption_age_privkey_path_list=[
        # Let the unit tests find controller.age in this file's parent directory.
        # Normally you would not want to keep it here,
        # as it would mean your controller key is in version control.
        (Path(__file__).parent.parent / "controller.age").as_posix(),
        # An example default value, not used in our unit tests
        "/default/path/for/nodes/key.age",
    ],
)
"""The method for storing secrets."""


def mint_version() -> str:
    """Mint a new version number

    This function is called by progfiguration core to generate a version number.
    It may be called by site-specific code as well.
    It should return a string that is a valid pip version number.
    (If you are also building other packages like RPMs,
    make sure to use a version number that is valid for all of them.)
    """
    from datetime import datetime

    dt = datetime.utcnow()
    epoch = int(dt.timestamp())
    version = f"1.0.{epoch}"
    return version