Rules

Base rule

class hammurabi.rules.base.Rule(name: str, param: Any, preconditions: Iterable[hammurabi.preconditions.base.Precondition] = (), pipe: Optional[Rule] = None, children: Iterable[Rule] = ())[source]

Abstract class which describes the bare minimum and helper functions for Rules. A rule defines what and how should be executed. Since a rule can have piped and children rules, the “parent” rule is responsible for those executions. This kind of abstraction allows to run both piped and children rules sequentially in a given order.

Example usage:

>>> from typing import Optional
>>> from pathlib import Path
>>> from hammurabi import Rule
>>> from hammurabi.mixins import GitMixin
>>>
>>> class SingleFileRule(Rule, GitMixin):
>>>     def __init__(self, name: str, path: Optional[Path] = None, **kwargs) -> None:
>>>         super().__init__(name, path, **kwargs)
>>>
>>>     def post_task_hook(self):
>>>         self.git_add(self.param)
>>>
>>>     @abstractmethod
>>>     def task(self) -> Path:
>>>         pass
Parameters
  • name (str) – Name of the rule which will be used for printing

  • param (Any) – Input parameter of the rule will be used as self.param

  • preconditions (Iterable["Rule"]) – “Boolean Rules” which returns a truthy or falsy value

  • pipe (Optional["Rule"]) – Pipe will be called when the rule is executed successfully

  • children (Iterable["Rule"]) – Children will be executed after the piped rule if there is any

Warning

Preconditions can be used in several ways. The most common way is to run “Boolean Rules” which takes a parameter and returns a truthy or falsy value. In case of a falsy return, the precondition will fail and the rule will not be executed.

If any modification is done by any of the rules which are used as a precondition, those changes will be committed.

Attributes

OwnerChanged

class hammurabi.rules.attributes.OwnerChanged(name: str, path: Optional[pathlib.Path] = None, new_value: Optional[str] = None, **kwargs)[source]

Change the ownership of a file or directory.

The new ownership of a file or directory can be set in three ways. To set only the user use new_value="username". To set only the group use new_value=":group_name" (please note the colon :). It is also possible to set both username and group at the same time by using new_value="username:group_name".

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, OwnerChanged
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         OwnerChanged(
>>>             name="Change ownership of nginx config",
>>>             path=Path("./nginx.conf"),
>>>             new_value="www:web_admin"
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

ModeChanged

class hammurabi.rules.attributes.ModeChanged(name: str, path: Optional[pathlib.Path] = None, new_value: Optional[int] = None, **kwargs)[source]

Change the mode of a file or directory.

Supported modes:

Config option

Description

stat.S_ISUID

Set user ID on execution.

stat.S_ISGID

Set group ID on execution.

stat.S_ENFMT

Record locking enforced.

stat.S_ISVTX

Save text image after execution.

stat.S_IREAD

Read by owner.

stat.S_IWRITE

Write by owner.

stat.S_IEXEC

Execute by owner.

stat.S_IRWXU

Read, write, and execute by owner.

stat.S_IRUSR

Read by owner.

stat.S_IWUSR

Write by owner.

stat.S_IXUSR

Execute by owner.

stat.S_IRWXG

Read, write, and execute by group.

stat.S_IRGRP

Read by group.

stat.S_IWGRP

Write by group.

stat.S_IXGRP

Execute by group.

stat.S_IRWXO

Read, write, and execute by others.

stat.S_IROTH

Read by others.

stat.S_IWOTH

Write by others.

stat.S_IXOTH

Execute by others.

Example usage:

>>> import stat
>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, ModeChanged
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         ModeChanged(
>>>             name="Update script must be executable",
>>>             path=Path("./scripts/update.sh"),
>>>             new_value=stat.S_IXGRP | stat.S_IXGRP | stat.S_IXOTH
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Directories

DirectoryExists

class hammurabi.rules.directories.DirectoryExists(name: str, path: Optional[pathlib.Path] = None, **kwargs)[source]

Ensure that a directory exists. If the directory does not exists, make sure the directory is created.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, DirectoryExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         DirectoryExists(
>>>             name="Create secrets directory",
>>>             path=Path("./secrets")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

DirectoryNotExists

class hammurabi.rules.directories.DirectoryNotExists(name: str, path: Optional[pathlib.Path] = None, **kwargs)[source]

Ensure that the given directory does not exists. In case the directory contains any file or sub-directory, those will be removed too.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, DirectoryNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         DirectoryNotExists(
>>>             name="Remove unnecessary directory",
>>>             path=Path("./temp")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

DirectoryEmptied

class hammurabi.rules.directories.DirectoryEmptied(name: str, path: Optional[pathlib.Path] = None, **kwargs)[source]

Ensure that the given directory’s content is removed. Please note the difference between emptying a directory and recreating it. The latter results in lost ACLs, permissions and modes.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, DirectoryEmptied
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         DirectoryEmptied(
>>>             name="Empty results directory",
>>>             path=Path("./test-results")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Files

FileExists

class hammurabi.rules.files.FileExists(name: str, path: Optional[pathlib.Path] = None, **kwargs)[source]

Ensure that a file exists. If the file does not exists, make sure the file is created.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, FileExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         FileExists(
>>>             name="Create service descriptor",
>>>             path=Path("./service.yaml")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

FilesExist

class hammurabi.rules.files.FilesExist(name: str, paths: Optional[Iterable[pathlib.Path]] = (), **kwargs)[source]

Ensure that all files exists. If the files does not exists, make sure the files are created.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, FilesExist
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         FilesExist(
>>>             name="Create test files",
>>>             paths=[
>>>                 Path("./file_1"),
>>>                 Path("./file_2"),
>>>                 Path("./file_3"),
>>>             ]
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

FileNotExists

class hammurabi.rules.files.FileNotExists(name: str, path: Optional[pathlib.Path] = None, **kwargs)[source]

Ensure that the given file does not exists. If the file exists remove it, otherwise do nothing and return the original path.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, FileNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         FileNotExists(
>>>             name="Remove unused file",
>>>             path=Path("./debug.yaml")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

FilesNotExist

class hammurabi.rules.files.FilesNotExist(name: str, paths: Optional[Iterable[pathlib.Path]] = (), **kwargs)[source]

Ensure that the given files does not exist. If the files exist remove them, otherwise do nothing and return the original paths.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, FilesNotExist
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         FilesNotExist(
>>>             name="Remove several files",
>>>             paths=[
>>>                 Path("./file_1"),
>>>                 Path("./file_2"),
>>>                 Path("./file_3"),
>>>             ]
>>>         ),
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

FileEmptied

class hammurabi.rules.files.FileEmptied(name: str, path: Optional[pathlib.Path] = None, **kwargs)[source]

Remove the content of the given file, but keep the file. Please note the difference between emptying a file and recreating it. The latter results in lost ACLs, permissions and modes.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, FileEmptied
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         FileEmptied(
>>>             name="Empty the check log file",
>>>             path=Path("/var/log/service/check.log")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Ini files

SectionExists

class hammurabi.rules.ini.SectionExists(name: str, path: Optional[pathlib.Path] = None, match: Optional[str] = None, options: Iterable[Tuple[str, Any]] = (), add_after: bool = True, **kwargs)[source]

Ensure that the given config section exists. If needed, the rule will create a config section with the given name, and optionally the specified options. In case options are set, the config options will be assigned to that config sections.

Similarly to hammurabi.rules.text.LineExists, this rule is able to add a section before or after a match section. The limitation compared to LineExists is that the SectionExists rule is only able to add the new entry exactly before or after its match.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, SectionExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         SectionExists(
>>>             name="Ensure section exists",
>>>             path=Path("./config.ini"),
>>>             section="polling",
>>>             match="add_after_me",
>>>             options=(
>>>                 ("interval", "2s"),
>>>                 ("abort_on_error", True),
>>>             ),
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

This rule requires the ini extra to be installed.

Warning

When using match be aware that partial matches will be recognized as well. This means you must be as strict with regular expressions as it is needed. Example of a partial match:

>>> import re
>>> pattern = re.compile(r"apple")
>>> text = "appletree"
>>> pattern.match(text).group()
>>> 'apple'

Warning

When options parameter is set, make sure you are using an iterable tuple. The option keys must be strings, but there is no limitation for the value. It can be set to anything what the parser can handle. For more information on the parser, please visit the documentation of configupdater.

SectionNotExists

class hammurabi.rules.ini.SectionNotExists(name: str, path: Optional[pathlib.Path] = None, section: Optional[str] = None, **kwargs)[source]

Make sure that the given file not contains the specified line. When a section removed, all the options belonging to it will be removed too.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, SectionNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         SectionNotExists(
>>>             name="Ensure section removed",
>>>             path=Path("./config.ini"),
>>>             section="invalid",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

This rule requires the ini extra to be installed.

SectionRenamed

class hammurabi.rules.ini.SectionRenamed(name: str, path: Optional[pathlib.Path] = None, new_name: Optional[str] = None, **kwargs)[source]

Ensure that a section is renamed. None of its options will be changed.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, SectionRenamed
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         SectionRenamed(
>>>             name="Ensure section renamed",
>>>             path=Path("./config.ini"),
>>>             section="polling",
>>>             new_name="fetching",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

This rule requires the ini extra to be installed.

OptionsExist

class hammurabi.rules.ini.OptionsExist(name: str, path: Optional[pathlib.Path] = None, options: Iterable[Tuple[str, Any]] = None, force_value: bool = False, **kwargs)[source]

Ensure that the given config option exists. If needed, the rule will create a config option with the given value. In case the force_value parameter is set to True, the original values will be replaced by the give ones.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, OptionsExist
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         OptionsExist(
>>>             name="Ensure options are changed",
>>>             path=Path("./config.ini"),
>>>             section="fetching",
>>>             options=(
>>>                 ("interval", "2s"),
>>>                 ("abort_on_error", True),
>>>             ),
>>>             force_value=True,
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

This rule requires the ini extra to be installed.

Warning

When using the force_value parameter, please note that all the existing option values will be replaced by those set in options parameter.

OptionsNotExist

class hammurabi.rules.ini.OptionsNotExist(name: str, path: Optional[pathlib.Path] = None, options: Iterable[str] = (), **kwargs)[source]

Remove one or more option from a section.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, OptionsNotExist
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         OptionsNotExist(
>>>             name="Ensure options are removed",
>>>             path=Path("./config.ini"),
>>>             section="invalid",
>>>             options=(
>>>                 "remove",
>>>                 "me",
>>>                 "please",
>>>             )
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

This rule requires the ini extra to be installed.

OptionRenamed

class hammurabi.rules.ini.OptionRenamed(name: str, path: Optional[pathlib.Path] = None, option: Optional[str] = None, new_name: Optional[str] = None, **kwargs)[source]

Ensure that an option of a section is renamed.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, OptionRenamed
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         OptionRenamed(
>>>             name="Rename an option",
>>>             path=Path("./config.ini"),
>>>             section="my_section",
>>>             option="typo",
>>>             new_name="correct",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

This rule requires the ini extra to be installed.

Json files

JsonKeyExists

class hammurabi.rules.json.JsonKeyExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[None, list, dict, str, int, float] = None, **kwargs)[source]

Ensure that the given key exists. If needed, the rule will create a key with the given name, and optionally the specified value. In case the value is set, the value will be assigned to the key. If no value is set, the key will be created with an empty value.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, JsonKeyExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         JsonKeyExists(
>>>             name="Ensure service descriptor has stack",
>>>             path=Path("./service.json"),
>>>             key="stack",
>>>             value="my-awesome-stack",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Note

The difference between KeyExists and ValueExists rules is the approach and the possibilities. While KeyExists is able to create values if provided, ValueExists rules are not able to create keys if any of the missing. KeyExists value parameter is a shorthand for creating a key and then adding a value to that key.

Warning

Compared to hammurabi.rules.text.LineExists, this rule is NOT able to add a key before or after a match.

JsonKeyNotExists

class hammurabi.rules.json.JsonKeyNotExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', **kwargs)[source]

Ensure that the given key not exists. If needed, the rule will remove a key with the given name, including its value.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, JsonKeyNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         JsonKeyNotExists(
>>>             name="Ensure outdated_key is removed",
>>>             path=Path("./service.json"),
>>>             key="outdated_key",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

JsonKeyRenamed

class hammurabi.rules.json.JsonKeyRenamed(name: str, path: Optional[pathlib.Path] = None, key: str = '', new_name: str = '', **kwargs)[source]

Ensure that the given key is renamed. In case the key can not be found, a LookupError exception will be raised to stop the execution. The execution must be stopped at this point, because if other rules depending on the rename they will fail otherwise.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, JsonKeyRenamed
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         JsonKeyRenamed(
>>>             name="Ensure service descriptor has dependencies",
>>>             path=Path("./service.json"),
>>>             key="development.depends_on",
>>>             value="dependencies",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

JsonValueExists

class hammurabi.rules.json.JsonValueExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[None, list, dict, str, int, float] = None, **kwargs)[source]

Ensure that the given key has the expected value(s). In case the key cannot be found, a LookupError exception will be raised to stop the execution.

This rule is special in the way that the value can be almost anything. For more information please read the warning below.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, JsonValueExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         JsonValueExists(
>>>             name="Ensure service descriptor has dependencies",
>>>             path=Path("./service.json"),
>>>             key="development.dependencies",
>>>             value=["service1", "service2", "service3"],
>>>         ),
>>>         # Or
>>>         JsonValueExists(
>>>             name="Add infra alerting to existing alerting components",
>>>             path=Path("./service.json"),
>>>             key="development.alerting",
>>>             value={"infra": "#slack-channel-2"},
>>>         ),
>>>         # Or
>>>         JsonValueExists(
>>>             name="Add support info",
>>>             path=Path("./service.json"),
>>>             key="development.supported",
>>>             value=True,
>>>         ),
>>>         # Or even
>>>         JsonValueExists(
>>>             name="Make sure that no development branch is set",
>>>             path=Path("./service.json"),
>>>             key="development.branch",
>>>             value=None,
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Note

The difference between KeyExists and ValueExists rules is the approach and the possibilities. While KeyExists is able to create values if provided, ValueExists rules are not able to create keys if any of the missing. KeyExists value parameter is a shorthand for creating a key and then adding a value to that key.

Warning

Since the value can be anything from None to a list of lists, and rule piping passes the 1st argument (path) to the next rule the value parameter can not be defined in __init__ before the path. Hence the value parameter must have a default value. The default value is set to None, which translates to the following:

Using the JsonValueExists rule and not assigning value to value parameter will set the matching key’s value to None` by default in the document.

JsonValueNotExists

class hammurabi.rules.json.JsonValueNotExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[str, int, float] = None, **kwargs)[source]

Ensure that the key has no value given. In case the key cannot be found, a LookupError exception will be raised to stop the execution.

Compared to hammurabi.rules.json.JsonValueExists, this rule can only accept simple value for its value parameter. No list, dict, or None can be used.

Based on the key’s value’s type if the value contains (or equals for simple types) value provided in the value parameter the value is:

  1. Set to None (if the key’s value’s type is not a dict or list)

  2. Removed from the list (if the key’s value’s type is a list)

  3. Removed from the dict (if the key’s value’s type is a dict)

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, JsonValueNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         JsonValueNotExists(
>>>             name="Remove decommissioned service from dependencies",
>>>             path=Path("./service.json"),
>>>             key="development.dependencies",
>>>             value="service4",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Operations

Moved

class hammurabi.rules.operations.Moved(name: str, path: Optional[pathlib.Path] = None, destination: Optional[pathlib.Path] = None, **kwargs)[source]

Move a file or directory from “A” to “B”.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, Moved
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         Moved(
>>>             name="Move pyproject.toml to its place",
>>>             path=Path("/tmp/generated/pyproject.toml.template"),
>>>             destination=Path("./pyproject.toml"),  # Notice the rename!
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Renamed

class hammurabi.rules.operations.Renamed(name: str, path: Optional[pathlib.Path] = None, new_name: Optional[str] = None, **kwargs)[source]

This rule is a shortcut for hammurabi.rules.operations.Moved. Instead of destination path a new name is required.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, Renamed
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         Renamed(
>>>             name="Rename pyproject.toml.bkp",
>>>             path=Path("/tmp/generated/pyproject.toml.bkp"),
>>>             new_name="pyproject.toml",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Copied

class hammurabi.rules.operations.Copied(name: str, path: Optional[pathlib.Path] = None, destination: Optional[pathlib.Path] = None, **kwargs)[source]

Ensure that the given file or directory is copied to the new path.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, Copied
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         Copied(
>>>             name="Create backup file",
>>>             path=Path("./service.yaml"),
>>>             destination=Path("./service.bkp.yaml")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Templates

TemplateRendered

class hammurabi.rules.templates.TemplateRendered(name: str, template: Optional[pathlib.Path] = None, destination: Optional[pathlib.Path] = None, context: Optional[Dict[str, Any]] = None, **kwargs)[source]

Render a file from a Jinja2 template. In case the destination file not exists, this rule will create it, otherwise the file will be overridden.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, TemplateRendered
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         TemplateRendered(
>>>             name="Create gunicorn config from template",
>>>             template=Path("/tmp/templates/gunicorn.conf.py"),
>>>             destination=Path("./gunicorn.conf.py"),
>>>             context={
>>>                 "keepalive": 65
>>>             },
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

This rule requires the templating extra to be installed.

Text files

LineExists

class hammurabi.rules.text.LineExists(name: str, path: Optional[pathlib.Path] = None, text: Optional[str] = None, match: Optional[str] = None, position: int = 1, respect_indentation: bool = True, ensure_trailing_newline: bool = False, **kwargs)[source]

Make sure that the given file contains the required line. This rule is capable for inserting the expected text before or after the unique match text respecting the indentation of its context.

The default behaviour is to insert the required text exactly after the match line, and respect its indentation. Please note that text``and ``match parameters are required.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, LineExists, IsLineNotExist
>>>
>>> gunicorn_config = Path("./gunicorn.conf.py")
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         LineExists(
>>>             name="Extend gunicorn config",
>>>             path=gunicorn_config,
>>>             text="keepalive = 65",
>>>             match=r"^bind.*",
>>>             preconditions=[
>>>                 IsLineNotExist(path=gunicorn_config, criteria=r"^keepalive.*")
>>>             ]
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

When using match be aware that partial matches will be recognized as well. This means you must be as strict with regular expressions as it is needed. Example of a partial match:

>>> import re
>>> pattern = re.compile(r"apple")
>>> text = "appletree"
>>> pattern.match(text).group()
>>> 'apple'

Note

The indentation of the match text will be extracted by a simple regular expression. If a more complex regexp is required, please inherit from this class.

LineNotExists

class hammurabi.rules.text.LineNotExists(name: str, path: Optional[pathlib.Path] = None, text: Optional[str] = None, **kwargs)[source]

Make sure that the given file not contains the specified line.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, LineNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         LineNotExists(
>>>             name="Remove keepalive",
>>>             path=Path("./gunicorn.conf.py"),
>>>             text="keepalive = 65",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

LineReplaced

class hammurabi.rules.text.LineReplaced(name: str, path: Optional[pathlib.Path] = None, text: Optional[str] = None, match: Optional[str] = None, respect_indentation: bool = True, **kwargs)[source]

Make sure that the given text is replaced in the given file.

The default behaviour is to replace the required text with the exact same indentation that the “match” line has. This behaviour can be turned off by setting the respect_indentation parameter to False. Please note that text and match parameters are required.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, LineReplaced
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         LineReplaced(
>>>             name="Replace typo using regex",
>>>             path=Path("./gunicorn.conf.py"),
>>>             text="keepalive = 65",
>>>             match=r"^kepalive.*",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Note

The indentation of the text will be extracted by a simple regular expression. If a more complex regexp is required, please inherit from this class.

Warning

When using match be aware that partial matches will be recognized as well. This means you must be as strict with regular expressions as it is needed. Example of a partial match:

>>> import re
>>> pattern = re.compile(r"apple")
>>> text = "appletree"
>>> pattern.match(text).group()
>>> 'apple'

Warning

This rule will replace all the matching lines in the given file. Make sure the given match regular expression is tested before the rule used against production code.

Yaml files

YamlKeyExists

class hammurabi.rules.yaml.YamlKeyExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[None, list, dict, str, int, float] = None, **kwargs)[source]

Ensure that the given key exists. If needed, the rule will create a key with the given name, and optionally the specified value. In case the value is set, the value will be assigned to the key. If no value is set, the key will be created with an empty value.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, YamlKeyExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         YamlKeyExists(
>>>             name="Ensure service descriptor has stack",
>>>             path=Path("./service.yaml"),
>>>             key="stack",
>>>             value="my-awesome-stack",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Note

The difference between KeyExists and ValueExists rules is the approach and the possibilities. While KeyExists is able to create values if provided, ValueExists rules are not able to create keys if any of the missing. KeyExists value parameter is a shorthand for creating a key and then adding a value to that key.

Warning

This rule requires the yaml extra to be installed.

Warning

Compared to hammurabi.rules.text.LineExists, this rule is NOT able to add a key before or after a match.

YamlKeyNotExists

class hammurabi.rules.yaml.YamlKeyNotExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', **kwargs)[source]

Ensure that the given key not exists. If needed, the rule will remove a key with the given name, including its value.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, YamlKeyNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         YamlKeyNotExists(
>>>             name="Ensure outdated_key is removed",
>>>             path=Path("./service.yaml"),
>>>             key="outdated_key",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

This rule requires the yaml extra to be installed.

YamlKeyRenamed

class hammurabi.rules.yaml.YamlKeyRenamed(name: str, path: Optional[pathlib.Path] = None, key: str = '', new_name: str = '', **kwargs)[source]

Ensure that the given key is renamed. In case the key can not be found, a LookupError exception will be raised to stop the execution. The execution must be stopped at this point, because if other rules depending on the rename they will fail otherwise.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, YamlKeyRenamed
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         YamlKeyRenamed(
>>>             name="Ensure service descriptor has dependencies",
>>>             path=Path("./service.yaml"),
>>>             key="development.depends_on",
>>>             value="dependencies",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

This rule requires the yaml extra to be installed.

YamlValueExists

class hammurabi.rules.yaml.YamlValueExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[None, list, dict, str, int, float] = None, **kwargs)[source]

Ensure that the given key has the expected value(s). In case the key cannot be found, a LookupError exception will be raised to stop the execution.

This rule is special in the way that the value can be almost anything. For more information please read the warning below.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, YamlValueExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         YamlValueExists(
>>>             name="Ensure service descriptor has dependencies",
>>>             path=Path("./service.yaml"),
>>>             key="development.dependencies",
>>>             value=["service1", "service2", "service3"],
>>>         ),
>>>         # Or
>>>         YamlValueExists(
>>>             name="Add infra alerting to existing alerting components",
>>>             path=Path("./service.yaml"),
>>>             key="development.alerting",
>>>             value={"infra": "#slack-channel-2"},
>>>         ),
>>>         # Or
>>>         YamlValueExists(
>>>             name="Add support info",
>>>             path=Path("./service.yaml"),
>>>             key="development.supported",
>>>             value=True,
>>>         ),
>>>         # Or even
>>>         YamlValueExists(
>>>             name="Make sure that no development branch is set",
>>>             path=Path("./service.yaml"),
>>>             key="development.branch",
>>>             value=None,
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Note

The difference between KeyExists and ValueExists rules is the approach and the possibilities. While KeyExists is able to create values if provided, ValueExists rules are not able to create keys if any of the missing. KeyExists value parameter is a shorthand for creating a key and then adding a value to that key.

Warning

This rule requires the yaml extra to be installed.

Warning

Since the value can be anything from None to a list of lists, and rule piping passes the 1st argument (path) to the next rule the value parameter can not be defined in __init__ before the path. Hence the value parameter must have a default value. The default value is set to None, which translates to the following:

Using the YamlValueExists rule and not assigning value to value parameter will set the matching key’s value to None` by default in the document.

YamlValueNotExists

class hammurabi.rules.yaml.YamlValueNotExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[str, int, float] = None, **kwargs)[source]

Ensure that the key has no value given. In case the key cannot be found, a LookupError exception will be raised to stop the execution.

Compared to hammurabi.rules.yaml.YamlValueExists, this rule can only accept simple value for its value parameter. No list, dict, or None can be used.

Based on the key’s value’s type if the value contains (or equals for simple types) value provided in the value parameter the value is:

  1. Set to None (if the key’s value’s type is not a dict or list)

  2. Removed from the list (if the key’s value’s type is a list)

  3. Removed from the dict (if the key’s value’s type is a dict)

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, YamlValueNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         YamlValueNotExists(
>>>             name="Remove decommissioned service from dependencies",
>>>             path=Path("./service.yaml"),
>>>             key="development.dependencies",
>>>             value="service4",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

This rule requires the yaml extra to be installed.

TOML files

Warning

In case of a single line toml file, the parser used in hammurabi will only keep the comment if the file contains a newline character.

TomlKeyExists

class hammurabi.rules.toml.TomlKeyExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[None, list, dict, str, int, float] = None, **kwargs)[source]

Ensure that the given key exists. If needed, the rule will create a key with the given name, and optionally the specified value. In case the value is set, the value will be assigned to the key. If no value is set, the key will be created with an empty value.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, TomlKeyExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         TomlKeyExists(
>>>             name="Ensure service descriptor has stack",
>>>             path=Path("./service.toml"),
>>>             key="stack",
>>>             value="my-awesome-stack",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Note

The difference between KeyExists and ValueExists rules is the approach and the possibilities. While KeyExists is able to create values if provided, ValueExists rules are not able to create keys if any of the missing. KeyExists value parameter is a shorthand for creating a key and then adding a value to that key.

Warning

Setting a value to None will result in a deleted key as per the documentation of how null/nil values should be handled. More info: https://github.com/toml-lang/toml/issues/30

Warning

Compared to hammurabi.rules.text.LineExists, this rule is NOT able to add a key before or after a match.

TomlKeyNotExists

class hammurabi.rules.toml.TomlKeyNotExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', **kwargs)[source]

Ensure that the given key not exists. If needed, the rule will remove a key with the given name, including its value.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, TomlKeyNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         TomlKeyNotExists(
>>>             name="Ensure outdated_key is removed",
>>>             path=Path("./service.toml"),
>>>             key="outdated_key",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

TomlKeyRenamed

class hammurabi.rules.toml.TomlKeyRenamed(name: str, path: Optional[pathlib.Path] = None, key: str = '', new_name: str = '', **kwargs)[source]

Ensure that the given key is renamed. In case the key can not be found, a LookupError exception will be raised to stop the execution. The execution must be stopped at this point, because if other rules depending on the rename they will fail otherwise.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, TomlKeyRenamed
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         TomlKeyRenamed(
>>>             name="Ensure service descriptor has dependencies",
>>>             path=Path("./service.toml"),
>>>             key="development.depends_on",
>>>             value="dependencies",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

TomlValueExists

class hammurabi.rules.toml.TomlValueExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[None, list, dict, str, int, float] = None, **kwargs)[source]

Ensure that the given key has the expected value(s). In case the key cannot be found, a LookupError exception will be raised to stop the execution.

This rule is special in the way that the value can be almost anything. For more information please read the warning below.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, TomlValueExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         TomlValueExists(
>>>             name="Ensure service descriptor has dependencies",
>>>             path=Path("./service.toml"),
>>>             key="development.dependencies",
>>>             value=["service1", "service2", "service3"],
>>>         ),
>>>         # Or
>>>         TomlValueExists(
>>>             name="Add infra alerting to existing alerting components",
>>>             path=Path("./service.toml"),
>>>             key="development.alerting",
>>>             value={"infra": "#slack-channel-2"},
>>>         ),
>>>         # Or
>>>         TomlValueExists(
>>>             name="Add support info",
>>>             path=Path("./service.toml"),
>>>             key="development.supported",
>>>             value=True,
>>>         ),
>>>         # Or even
>>>         TomlValueExists(
>>>             name="Make sure that no development branch is set",
>>>             path=Path("./service.toml"),
>>>             key="development.branch",
>>>             value=None,
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Note

The difference between KeyExists and ValueExists rules is the approach and the possibilities. While KeyExists is able to create values if provided, ValueExists rules are not able to create keys if any of the missing. KeyExists value parameter is a shorthand for creating a key and then adding a value to that key.

Warning

Since the value can be anything from None to a list of lists, and rule piping passes the 1st argument (path) to the next rule the value parameter can not be defined in __init__ before the path. Hence the value parameter must have a default value. The default value is set to None, which translates to the following:

Using the TomlValueExists rule and not assigning value to value parameter will set the matching key’s value to None` by default in the document.

TomlValueNotExists

class hammurabi.rules.toml.TomlValueNotExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[str, int, float] = None, **kwargs)[source]

Ensure that the key has no value given. In case the key cannot be found, a LookupError exception will be raised to stop the execution.

Compared to hammurabi.rules.Toml.TomlValueExists, this rule can only accept simple value for its value parameter. No list, dict, or None can be used.

Based on the key’s value’s type if the value contains (or equals for simple types) value provided in the value parameter the value is:

  1. Set to None (if the key’s value’s type is not a dict or list)

  2. Removed from the list (if the key’s value’s type is a list)

  3. Removed from the dict (if the key’s value’s type is a dict)

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, TomlValueNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         TomlValueNotExists(
>>>             name="Remove decommissioned service from dependencies",
>>>             path=Path("./service.toml"),
>>>             key="development.dependencies",
>>>             value="service4",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)