Custom item types¶
Step 1: Create an item module¶
Create a new file called /your/bundlewrap/repo/items/foo.py
. You can use this as a template:
from bundlewrap.items import Item, ItemStatus
class Foo(Item):
"""
A foo.
"""
BLOCK_CONCURRENT = []
BUNDLE_ATTRIBUTE_NAME = "foo"
NEEDS_STATIC = []
ITEM_ATTRIBUTES = {
'attribute': "default value",
}
ITEM_TYPE_NAME = "foo"
REQUIRED_ATTRIBUTES = ['attribute']
def __repr__(self):
return "<Foo attribute:{}>".format(self.attributes['attribute'])
def ask(self, status):
"""
Returns a string asking the user if this item should be
implemented.
"""
return ""
def fix(self, status):
"""
Do whatever is necessary to correct this item.
"""
raise NotImplementedError
def get_status(self):
"""
Returns an ItemStatus instance describing the current status of
the item on the actual node. Must not be cached.
"""
return ItemStatus(
correct=True,
description="No description available.",
info={},
)
Step 2: Define attributes¶
BUNDLE_ATTRIBUTE_NAME
is the name of the variable defined in a bundle module that holds the items of this type. If your bundle looks like this:
foo = { [...] }
...then you should put BUNDLE_ATTRIBUTE_NAME = "foo"
here.
NEEDS_STATIC
is a list of hard-wired dependencies for all intances of your item. For example, all services inherently depend on all packages (because you can’t start the service without installing its package first). Most of the time, this will be a wildcard dependency on a whole type of items, not a specific one:
NEEDS_STATIC = ["file:/etc/hosts", "user:"] # depends on /etc/hosts and all users
ITEM_ATTRIBUTES
is a dictionary of the attributes users will be able to configure for your item. For files, that would be stuff like owner, group, and permissions. Every attribute (even if it’s mandatory) needs a default value, None
is totally acceptable:
ITEM_ATTRIBUTES = {'attr1': "default1"}
ITEM_TYPE_NAME
sets the first part of an items ID. For the file items, this is “file”. Therefore, file ID look this this: file:/path
. The second part is the name a user assigns to your item in a bundle. Example:
ITEM_TYPE_NAME = "foo"
BLOCK_CONCURRENT
is a list of item types (e.g. pkg_apt
), that cannot be applied in parallel with this type of item. May include this very item type itself. For most items this is not an issue (e.g. creating multiple files at the same time), but some types of items have to be applied sequentially (e.g. package managers usually employ locks to ensure only one package is installed at a time):
BLOCK_CONCURRENT = ["pkg_apt"]
REQUIRED_ATTRIBUTES
is a list of attribute names that must be set on each item of this type. If BundleWrap encounters an item without all these attributes during bundle inspection, an exception will be raised. Example:
REQUIRED_ATTRIBUTES = ['attr1', 'attr2']
Step 3: Implement methods¶
You should probably start with get_status
. Use self.node.run("command")
to run shell commands on the current node and check the stdout
property of the returned object. The info dict passed to ItemStatus
can be filled with arbitrary information on how to efficiently fix the item.
Next up is the ask
method. It must return a string containing all information a user needs in interactive mode to decide whether they want to apply the item or not and offer a preview of all changes that would be made.
Finally, the fix
method doesn’t have to return anything and just uses self.node.run()
to fix the item. To do this efficiently, it may use the status.info
dict you built earlier.