Carthage Modeling Module

Base Models

class carthage.modeling.CarthageLayout(**kwargs)

CarthageLayout is typically the top level class in a set of Carthage models. It is a ModelGroup that represents a complete collection of objects modeled by Carthage. The primary purpose of this class is to signify the top of a collection of objects; the layout is generally the place to start when examining a collection of models.

However, a layout differs from a ModelGroup in two ways:

  1. carthage-runner looks for a CarthageLayout to instantiate after loading plugins. If the console is used, the layout is made available in the layout local variable of the console. If a command is run, the command is run in the context of the layout.

  2. Layouts that set the carthage.kvstore.persistent_seed_path in the context of their Injector will have persistent assignments of things like IP addresses and MAC addresses loaded from the seed path when instantiated.

classmethod default_class_injection_key()

Called by Injector.add_provider() in the single argument form to get the injection key to use when this class is added to provide a dependency.

layout_uuid()

Returns a uuid for this layout; based on layout_name or the class’s qualname.

class carthage.modeling.InjectableModel(*args, _not_transcluded=None, **kwargs)
class carthage.modeling.MachineModel(**kwargs)

Represents the aspects of a Machine that are independent of the implementation of that machine. Typically includes things like:

  • Network configuration (NetworkConfig

  • Configuration for devops tools like Ansible

  • Selecting the target implementation (whether the machine will be a VM, container, or hosted on real hardware)

Applications that want to reason about the abstract environment typically only need to instantiate models. Applications that want to build VMs or effect changes to real hardware instantiate the machine implementations. This class is the modeling extensions to carthage.machine.AbstractMachineModel.

If a MachineModel contains reference to setup_tasks, then it will automatically gain SetupTaskMixin as a base class. Similarly, if model_mixin_for() is used to decorate a class in the same CarthageLayout encountered before the MachineModel, then that class will be added as an implicit base class of the model.

Class Parameters passed in as keywords on the class statement:

param template:

True if this class represents a base class or mixin rather than a actual model of a specific machine.

param mixin_key:

The InjectionKey to use to search for mixins.

Any carthage.network.NetworkConfig present in the MachineModel will be used as the network configuration.

Every carthage.machine.BaseCustomization (including MachineCustomization, FilesystemCustomization and ContainerCustomization) will be integrated into the resulting machine:

  • If the customization is called cust, then a method will be added to the machine cust_task which is a carthage.machine.customization_task() to run the customization.

  • On the model, cust will become a method that will produce an instance of the customization applied to the machine.

For example:

class server(MachineModel):

    class install_software(MachineCustomization):

        webserver_role = ansible_role_task('webserver')

        database_role = ansible_role_task('database')

Then server.machine will have a method install_software_task which will run both ansible roles assuming they have not already been run. model.install_software will produce an instance of the customization applied to model.machine. model.install_software.database_role() is a method that will force the database_role to run even if it appears up to date.

ansible_groups: Sequence[str]

A set of ansible groups to add a model to; see carthage.modeling.ansible.enable_modeling_ansible().

machine_mixins = ()

Sequence of classes to be mixed into the resulting machine implementation

classmethod our_key()

Returns the globally unique InjectionKey by which this model is known.

async resolve_networking(*args, **kwargs)

See resolve_networking() for documentation.

In adition to the standard behavior, if machine_type() is an instance of LocalMachineMixin,

then call carthage.local.process_local_network_config() to learn about local bridges.

stamp_subdir()

The part of stamp_path() after config.stamp_dir. For example for a podman container called example.com this might be podman/containers/example.com

classmethod supplementary_injection_keys(k)

Exclude AbstractMachineModel and MachineModel without constraints from the set of keys that are registered. These keys are searched to find the current MachineModel, and if they are implicitly set it can produce cases where a MachineModel is accidentally available. Also, it makes it difficult to have one MachineModel contained within another.

class carthage.modeling.ModelContainer(*args, _not_transcluded=None, **kwargs)

Decorators

carthage.modeling.decorators.dynamic_name(name)

A decorator to be used in a modeling type to supply a dynamic name. Example:

for i in range(3):
    @dynamic_name(f'i{i+1}')

class ignored: square = i*i

Will define threevariables i1 through i3, with values of squares (i1 = 0, i2 =1, i3 = 4).

carthage.modeling.decorators.globally_unique_key(key: InjectionKey | Callable[[object], InjectionKey])

Decorate a value to indicate that key is a globally unique InjectionKey that should provide the given value. Globally unique keys are not extended with additional constraints when propagated up through ModelingContainers.

Parameters:

key – A callback that maps the value to a key. Alternatively, simply the InjectionKey to use.

class carthage.modeling.decorators.injector_access(key, target=None)

Usage:

val = injector_access("foo")

At runtime, model_instance.val will be memoized to model_instance.injector.get_instance("foo").

__call__(injector: Injector)

Call self as a function.

key: InjectionKey

The injection key to be accessed.

carthage.modeling.decorators.machine_mixin(name=None)

Mark a class (subclass of Machine typically) as something that should be mixed in to any machine declared lower in the injector hierarchy withing modeling classes. To accomplish the same thing outside of modeling classes:

injector.add_provider(InjectionKey(MachineMixin, name = "some_name"), dependency_quote(mixin_class))

The call to :func:~carthage.dependency_injection.dependency_quote` is required to prevent the injector from trying to build a machine when the Mixin is looked up.

carthage.modeling.decorators.model_mixin_for(**constraints)

Indicate that a given model supplements a model declared automatically. The simplest usage looks like:

@model_mixin_for(host = "foo.com")
class foomixin(MachineModel):
    # stuff added to foo.com

class foo(MachineModel):
    name = "foo.com"
    # Also inherits from foomixin

In the above example it would be simpler to list foomixin as a base for foo. However when a loop instantiates a number of models with a dynamic name, model_mixin_for provides value:

for c in ('foo', 'bar', 'baz'):
    @dynamic_name(c)
    class model(MachineModel):
        name = c+".com"

In the above usage, the loop would need to get more complicated to only add foomixin to the dynamically generated class for the foo.com model. In this case model_mixin_for provides value.

IN a more complex usage, model_mixin_for can be used in a layout that will transclude a model using carthage.modeling.base.model_bases(). This is similar to transclude_overrides() except that rather than entirely replacing the model, model_mixin_for simply adds a base.

carthage.modeling.decorators.propagate_key(key, obj=None)

Indicate a set of keys that should be propagated up in a container:

class foo(ModelingContainer):
    @propagate_key(InjectionKey(Baz, target = 42))
    class ourbaz(Baz): ...

When foo is included in a container, then the Baz injection key will be propagated up to dependencies provided by that container. Since the key was not marked globally unique, constraints from foo.our_key() will be added to it as it is propagated.

keys are also provided by the contained class as if provides() or globally_unique() were called.

Propagating a key up is typically an interface point; rather than propagating all keys related to an object up, propagate the keys that will be understood by the environment. Examples of usage include:

  • Any AbstractMachineModel with a host constraint is collected to find all the machine models in a layout

carthage.modeling.decorators.provides(*keys)

Indicate that the decorated value provides these InjectionKeys

carthage.modeling.decorators.transclude_overrides(key: InjectionKey = None)

Decorator indicating that the decorated item should be overridden by one from the injector against which the containing layout is eventually instantiated. When a InjectableModel is eventually instantiated, before an overridable key is added to the local injector, it is searched for in the instantiating injector. If it is found, the key is not registered. This has the effect of using the dependency provider in the instantiating injector rather than the one included in the layout.

Parameters:

key – If supplied, is the key expected to be registered with the transcluding injector to override this object. If not supplied, the object must have a globally unique key.

Example usage:

class layout(CarthageLayout):

    @transclude_overrides(key=InjectionKey("network"))
    class network(NetworkModel):
        # ...

If layout is instantiated in a injector that provides a dependency for InjectionKey('network'), then that object will be used rather than the network class within the layout. Since the network property is an injector_acess(), layout_instance.network will refer to the object in the instantiating injector rather than an instance of layout.network.