SLS Resolver Plugins - hub.source#

Idem SLS resolvers are very easy to write and can often work in just a few lines of code. They are implemented in a nested pop subsystem under idem, this means that you can vertical app-merge these plugins into idem if that makes the most sense.

The vast majority of the work to gather SLS files is completely generic, so adding new sources should be very easy. For instance, the code to load from the filesystem is just a few lines of code.

First create a directory at my_project_root/my_provider/source. In your project’s conf.py, extend idem’s namespace with your “source” directory.

# my_project_root/my_provider/conf.py
DYNE = {"source": ["source"]}

Now create a plugin in your “source” directory.

The plugin simply needs to implement the cache async function. This function receives the hub, protocol, source, and loc. These will be passed into the function.

The protocol is usually just the name of the resolver plugin. Sometimes it will contain an additional protocol like “git+https”

The source is the root path to pull the file from. In the case of the file resolver this will be file:///path/to/sls/root.

The location or location is the desired file location relative to the source.

# my_project_root/my_provider/source/my_provider.py

# Generic python imports
from typing import ByteString
from typing import Tuple
import tempfile

try:
    # Import plugin-specific libraries here
    import my_provider.sdk

    HAS_LIBS = (True,)
except ImportError as e:
    HAS_LIBS = False, str(e)


def __virtual__(hub):
    # Perform other dependency checks as needed here I.E check for "git" or "fuse" installed via the OS
    return HAS_LIBS


# The virtualname is how the provider will appear on the hub,
# use this to avoid clashes with python module names and python keywords
__virtualname__ = "my_provider"


def __init__(hub):
    # This tells idem that the "my_provider" key in the acct file contains profiles for this plugin
    hub.source.my_provider.ACCT = ["my_provider"]


async def cache(
    hub, ctx, protocol: str, source: str, location: str
) -> Tuple[str, ByteString]:
    """
    Read data from my_provider
    :param hub:
    :param ctx: ctx.acct will contain the appropriate credentials to connect to my sls source's sdk
    :param source: The url/path/location of this sls source
    :param location: The path/key to access my SLS data relative to the source


    Define what credentials should be used in a profile for "my_provider"

    .. code-block:: sls

        my_provider:
          my_profile:
            my_kwarg_1: my_val_1
            other_arbitrary_kwarg: arbitrary_value
    """
    # ctx.acct will have the credentials for the profile specified in the sls-sources/params-sources string
    data: bytes = my_provider.sdk.read(**ctx.acct)

    # There are two ways we can pass the sources on to idem
    if "option 1":
        # Store the data returned from the sdk in memory (preferred method)
        return f"{location}.sls", data
    elif "option 2":
        # Cache the data returned from the sdk in a temporary location
        with tempfile.NamedTemporaryFile(suffix=".sls", delete=True) as fh:
            fh.write(data)
            fh.flush()
            # Process the cached data like a traditional sls file source
            return await hub.source.file.cache(
                ctx=None, protocol="file", source=fh.name, location=location
            )

Your sls source can be invoked from the CLI like so:

$ idem state location --sls-sources "my_provider://my_profile@<source>" --params-sources "my_provider://my_profile@<source>"