diff --git a/examples/git-using-lib/git_using_lib.py b/examples/git-using-lib/git_using_lib.py index b6e203f..d2033bb 100644 --- a/examples/git-using-lib/git_using_lib.py +++ b/examples/git-using-lib/git_using_lib.py @@ -6,7 +6,7 @@ class GitPromiseTypeModule(PromiseModule): def __init__(self, **kwargs): super().__init__("git_promise_module", "0.0.3", **kwargs) - def validate_promise(self, promiser, attributes, meta): + def validate_promise(self, promiser, attributes, metadata): if not promiser.startswith("/"): raise ValidationError(f"File path '{promiser}' must be absolute") for name, value in attributes.items(): @@ -15,7 +15,7 @@ def validate_promise(self, promiser, attributes, meta): if name == "repo" and type(value) is not str: raise ValidationError(f"'repo' must be string for git promise types") - def evaluate_promise(self, promiser, attributes, meta): + def evaluate_promise(self, promiser, attributes, metadata): folder = promiser url = attributes["repo"] diff --git a/examples/gpg/gpg.py b/examples/gpg/gpg.py index 0b002c9..86c68a5 100644 --- a/examples/gpg/gpg.py +++ b/examples/gpg/gpg.py @@ -103,7 +103,7 @@ def gpg_key_present(self, homedir, user_id): proc.communicate() self.log_error(f"Timed out querying for gpg key '{user_id}'") - def validate_promise(self, promiser, attributes, meta): + def validate_promise(self, promiser, attributes, metadata): if not promiser.startswith("/"): raise ValidationError( f"Promiser '{promiser}' for 'gpg_keys' promise must be an absolute path" @@ -113,7 +113,7 @@ def validate_promise(self, promiser, attributes, meta): f"Required attribute 'keylist' missing for 'gpg_keys' promise" ) - def evaluate_promise(self, promiser, attributes, meta): + def evaluate_promise(self, promiser, attributes, metadata): keylist_json = self.clean_storejson_output(attributes["keylist"]) self.log_verbose(f"keylist_json is '{keylist_json}'") diff --git a/examples/rss/rss.py b/examples/rss/rss.py index c95b007..0ef6a4c 100755 --- a/examples/rss/rss.py +++ b/examples/rss/rss.py @@ -8,7 +8,7 @@ def __init__(self): super().__init__("rss_promise_module", "0.0.3") - def validate_promise(self, promiser, attributes, meta): + def validate_promise(self, promiser, attributes, metadata): # check promiser type if type(promiser) is not str: raise ValidationError("invalid type for promiser: expected string") @@ -43,7 +43,7 @@ def validate_promise(self, promiser, attributes, meta): raise ValidationError(f"Invalid value '{select}' for attribute select: must be newest, oldest or random") - def evaluate_promise(self, promiser, attributes, meta): + def evaluate_promise(self, promiser, attributes, metadata): # get attriute feed feed = attributes['feed'] diff --git a/examples/site-up/site_up.py b/examples/site-up/site_up.py index 5341aa0..0cdd447 100644 --- a/examples/site-up/site_up.py +++ b/examples/site-up/site_up.py @@ -9,11 +9,11 @@ class SiteUpPromiseTypeModule(PromiseModule): def __init__(self): super().__init__("site_up_promise_module", "0.0.2") - def validate_promise(self, promiser, attributes, meta): + def validate_promise(self, promiser, attributes, metadata): if not self.is_url_valid(promiser): raise ValidationError(f"URL '{promiser}' is invalid") - def evaluate_promise(self, url, attributes, meta): + def evaluate_promise(self, url, attributes, metadata): ssl_ctx = ssl.create_default_context() if ( "skip_ssl_verification" in attributes diff --git a/libraries/python/README.md b/libraries/python/README.md new file mode 100644 index 0000000..5fa9eca --- /dev/null +++ b/libraries/python/README.md @@ -0,0 +1,16 @@ +A library for CFEngine custom promise types implemented in Python. +It provides the `PromiseModule` base class and many helper values and mechanisms that facilitates creation of custom promises. + +If you'd like to develop new promise types (or other modules) for CFEngine, we recommend finishing our getting started tutorial: + +https://docs.cfengine.com/docs/3.21/getting-started.html + +(The last part shows implementing your first module). + +If you feel comfortable with using CFEngine and writing Python code already, there is a template project you can just copy and start editing: + +https://github.com/cfengine/promise-type-template + +For more detailed information about how custom promise types work, see our documentation: + +https://docs.cfengine.com/docs/3.21/reference-promise-types-custom.html diff --git a/libraries/python/README.org b/libraries/python/README.org deleted file mode 100644 index ba9cb34..0000000 --- a/libraries/python/README.org +++ /dev/null @@ -1,7 +0,0 @@ -* CFEngine Python promise types library - -A library for CFEngine custom promise types implemented in Python. It provides -the =PromiseModule= base class and many helper values and mechanisms that -facilitates creation of custom promises. - -See [[https://github.com/cfengine/modules/blob/master/examples/git-using-lib/git_using_lib.py][examples/git-using-lib]] for an example of how this library can be used. diff --git a/libraries/python/cfengine.py b/libraries/python/cfengine.py index 8f45447..b8a8660 100644 --- a/libraries/python/cfengine.py +++ b/libraries/python/cfengine.py @@ -326,10 +326,10 @@ def _handle_init(self): _put_response(self._response, self._out, self._record_file) def _handle_validate(self, promiser, attributes, request): - meta = {"promise_type": request.get("promise_type")} + metadata = {"promise_type": request.get("promise_type")} try: - self.validate_attributes(promiser, attributes, meta) - returned = self.validate_promise(promiser, attributes, meta) + self.validate_attributes(promiser, attributes, metadata) + returned = self.validate_promise(promiser, attributes, metadata) if returned is None: # Good, expected self._result = Result.VALID @@ -370,9 +370,9 @@ def _handle_validate(self, promiser, attributes, request): def _handle_evaluate(self, promiser, attributes, request): self._result_classes = None - meta = {"promise_type": request.get("promise_type")} + metadata = {"promise_type": request.get("promise_type")} try: - results = self.evaluate_promise(promiser, attributes, meta) + results = self.evaluate_promise(promiser, attributes, metadata) # evaluate_promise should return either a result or a (result, result_classes) pair if type(results) == str: @@ -448,16 +448,16 @@ def prepare_promiser_and_attributes(self, promiser, attributes): """Override if you want to modify promiser or attributes before validate or evaluate""" return (promiser, attributes) - def validate_attributes(self, promiser, attributes, meta): + def validate_attributes(self, promiser, attributes, metadata): """Override this if you want to prevent automatic validation""" return self._validate_attributes(promiser, attributes) - def validate_promise(self, promiser, attributes, meta): + def validate_promise(self, promiser, attributes, metadata): """Must override this or use validation through self.add_attribute()""" if not self._has_validation_attributes: raise NotImplementedError("Promise module must implement validate_promise") - def evaluate_promise(self, promiser, attributes, meta): + def evaluate_promise(self, promiser, attributes, metadata): raise NotImplementedError("Promise module must implement evaluate_promise") def protocol_terminate(self): diff --git a/promise-types/ansible/README.md b/promise-types/ansible/README.md index 0615eb5..c6e3cba 100644 --- a/promise-types/ansible/README.md +++ b/promise-types/ansible/README.md @@ -1,35 +1,6 @@ -# ansible promise module +The `ansible` promise type allows you to run Ansible playbooks from within CFEngine policy. -## Synopsis - -* *Name*: `ansible` -* *Version*: `0.1.1` -* *Description*: Run Ansible playbooks - -## Requirements - -* Ansible >= 2.8.0 - -## Attributes - -| Name | Type | Description| Mandatory | Default | -| --- | --- | --- | --- | --- | -| `playbook` | `string` | Absolute path of the Ansible playbook | No | Promiser | -| `inventory` | `string` | Absolute path of the inventory file | No | - | -| `limit` | `slist` | List of host names to target | No | `{"localhost"}` | -| `tags` | `slist` | List of tags to play | No | `{}` | -| `become` | `boolean` | Set the `become` option | No | `False` | -| `become_method` | `string` | Set the `become_method` option | No | `"sudo"` | -| `become_user` | `string` | Set the `become_user` option | No | `root` | -| `connection` | `string` | Set the `connection` option; possible values: `local`, `ssh` | No | `local` | -| `forks` | `int` | Set the `forks` option | No | `1` | -| `private_key_file` | `string` | Absolute path of the SSH private key to use | No | - | -| `remote_user` | `string` | Set the `remote_user` option | No | `root` | - -## Examples - -Play the `/northern.tech/playbook.yaml` playbook locally, using `/northern.tech/inventory.yaml`, and limiting -the execution to the `helloworld` tag: +For example, you can play the `/northern.tech/playbook.yaml` playbook locally, using `/northern.tech/inventory.yaml`, and limiting the execution to the `helloworld` tag: ```cfengine3 bundle agent main @@ -42,6 +13,28 @@ bundle agent main } ``` +## Requirements + +* Ansible >= 2.8.0 + +## Attributes + +| Name | Type | Description | Mandatory | Default | +| ------------------ | --------- | ------------------------------------------------------------ | --------- | --------------- | +| `playbook` | `string` | Absolute path of the Ansible playbook | No | Promiser | +| `inventory` | `string` | Absolute path of the inventory file | No | - | +| `limit` | `slist` | List of host names to target | No | `{"localhost"}` | +| `tags` | `slist` | List of tags to play | No | `{}` | +| `become` | `boolean` | Set the `become` option | No | `False` | +| `become_method` | `string` | Set the `become_method` option | No | `"sudo"` | +| `become_user` | `string` | Set the `become_user` option | No | `root` | +| `connection` | `string` | Set the `connection` option; possible values: `local`, `ssh` | No | `local` | +| `forks` | `int` | Set the `forks` option | No | `1` | +| `private_key_file` | `string` | Absolute path of the SSH private key to use | No | - | +| `remote_user` | `string` | Set the `remote_user` option | No | `root` | + +## Examples + This promise can run ansible over ssh targeting multiple hosts, for example: ```cfengine3 @@ -64,9 +57,8 @@ bundle agent main ## Authors -This software was created by the team at [Northern.tech AS](https://northern.tech), with many contributions from the community. Thanks everyone! - -[CFEngine](https://cfengine.com) is sponsored by [Northern.tech AS](https://northern.tech) +This software was created by the team at [Northern.tech](https://northern.tech), with many contributions from the community. +Thanks everyone! ## Contribute diff --git a/promise-types/ansible/ansible_promise.py b/promise-types/ansible/ansible_promise.py index 2fb55df..115ac77 100644 --- a/promise-types/ansible/ansible_promise.py +++ b/promise-types/ansible/ansible_promise.py @@ -73,7 +73,7 @@ def v2_playbook_on_stats(self, stats): class AnsiblePromiseTypeModule(PromiseModule): def __init__(self, **kwargs): super(AnsiblePromiseTypeModule, self).__init__( - "ansible_promise_module", "0.2.1", **kwargs + "ansible_promise_module", "0.2.2", **kwargs ) def must_be_absolute(v): @@ -98,12 +98,12 @@ def prepare_promiser_and_attributes(self, promiser, attributes): safe_promiser = promiser.replace(",", "_") return (safe_promiser, attributes) - def validate_promise(self, promiser: str, attributes: Dict, meta: Dict): + def validate_promise(self, promiser: str, attributes: Dict, metadata: Dict): if not ANSIBLE_AVAILABLE: raise ValidationError("Ansible Python module not available") def evaluate_promise( - self, safe_promiser: str, attributes: Dict, meta: Dict + self, safe_promiser: str, attributes: Dict, metadata: Dict ) -> Tuple[str, List[str]]: model = self.create_attribute_object(safe_promiser, attributes) diff --git a/promise-types/git/README.md b/promise-types/git/README.md index f15964f..529d753 100644 --- a/promise-types/git/README.md +++ b/promise-types/git/README.md @@ -1,9 +1,4 @@ -# git promise type - -## Synopsis - -* *Name*: `git` -* *Description*: Manage git checkouts of repositories to deploy files or software. +The `git` promise type enables writing concise policy for cloning a git repo and keeping it updated. ## Requirements @@ -67,9 +62,8 @@ bundle agent main ## Authors -This software was created by the team at [Northern.tech AS](https://northern.tech), with many contributions from the community. Thanks everyone! - -[CFEngine](https://cfengine.com) is sponsored by [Northern.tech AS](https://northern.tech) +This software was created by the team at [Northern.tech](https://northern.tech), with many contributions from the community. +Thanks everyone! ## Contribute diff --git a/promise-types/git/git.py b/promise-types/git/git.py index e472948..1576748 100644 --- a/promise-types/git/git.py +++ b/promise-types/git/git.py @@ -9,7 +9,7 @@ class GitPromiseTypeModule(PromiseModule): def __init__(self, **kwargs): super(GitPromiseTypeModule, self).__init__( - "git_promise_module", "0.2.2", **kwargs + "git_promise_module", "0.2.3", **kwargs ) def destination_must_be_absolute(v): @@ -42,7 +42,7 @@ def depth_must_be_zero_or_more(v): self.add_attribute("update", bool, default=True) self.add_attribute("version", str, default="HEAD") - def evaluate_promise(self, promiser: str, attributes: Dict, meta: Dict): + def evaluate_promise(self, promiser: str, attributes: Dict, metadata: Dict): safe_promiser = promiser.replace(",", "_") attributes.setdefault("destination", promiser) model = self.create_attribute_object(promiser, attributes) diff --git a/promise-types/groups/README.md b/promise-types/groups/README.md index d82d0eb..b2637c7 100644 --- a/promise-types/groups/README.md +++ b/promise-types/groups/README.md @@ -1,11 +1,6 @@ -# groups promise type +The `groups` promise type helps managing local groups, letting you ensure some users are a part of a group, or excluded from a group, etc. -## Synopsis - -* *Name*: `groups` -* *Version*: `0.1.3` -* *Description*: Manage local groups. -* *Note*: This is an experimental version of a promise type, and may be changed in the future. +**Note**: This is an experimental version of a promise type, and may be changed in the future. ## Requirements @@ -82,9 +77,8 @@ bundle agent main ## Authors -This software was created by the team at [Northern.tech AS](https://northern.tech), with many contributions from the community. Thanks everyone! - -[CFEngine](https://cfengine.com) is sponsored by [Northern.tech AS](https://northern.tech) +This software was created by the team at [Northern.tech](https://northern.tech), with many contributions from the community. +Thanks everyone! ## Contribute diff --git a/promise-types/groups/groups.py b/promise-types/groups/groups.py index e11b645..111cf57 100644 --- a/promise-types/groups/groups.py +++ b/promise-types/groups/groups.py @@ -6,11 +6,11 @@ class GroupsPromiseTypeModule(PromiseModule): def __init__(self): - super().__init__("groups_promise_module", "0.2.3") + super().__init__("groups_promise_module", "0.2.4") self._name_regex = re.compile(r"^[a-z_][a-z0-9_-]*[$]?$") self._name_maxlen = 32 - def validate_promise(self, promiser, attributes, meta): + def validate_promise(self, promiser, attributes, metadata): # check promiser value if self._name_regex.match(promiser) is None: self.log_warning( @@ -115,7 +115,7 @@ def validate_promise(self, promiser, attributes, meta): % (duplicates, promiser) ) - def evaluate_promise(self, promiser, attributes, meta): + def evaluate_promise(self, promiser, attributes, metadata): # keep track of any repairs or failed repairs failed_repairs = 0 repairs = 0 diff --git a/promise-types/http/README.org b/promise-types/http/README.org index cbfbb55..8feab95 100644 --- a/promise-types/http/README.org +++ b/promise-types/http/README.org @@ -1,22 +1,24 @@ -* http promise type +The =http= promise type lets you perform HTTP(S) requests from policy. -** Synopsis +** Example -- /Name/: =http= -- /Version/: =1.0.0= -- /Description/: Perform HTTP(S) requests from policy. +Here is a simple example of downloading an SVG file (with a HTTP GET request): -** Promiser and attributes - -The promiser can either be a URL of the request or, if the /url/ attribute is -used (see below), it can be an arbitrary string. All attributes are optional if -the URL is specified in the promiser. +#+BEGIN_SRC cfengine3 +bundle agent __main__ +{ + http: + "https://cfengine.com/images/cfengine-logo.svg" + file => "/var/cfengine/cfengine-logo.svg", + if => not(fileexists("/var/cfengine/cfengine-logo.svg")); +} +#+END_SRC *** Attributes | Name | Type | Description | Mandatory | Default | |------------+-----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+------------| -| =url= | =string= | URL or the request (starting with "http://" or "https://") | No | /promiser/ | +| =url= | =string= | URL of the request (starting with "http://" or "https://") | No | /promiser/ | | =file= | =string= | File system path for where to save the response (body). | No | - | | =method= | =string= | HTTP method of the request (GET, POST, PUT, DELETE or PATCH) | No | - | | =headers= | =string=, =slist= or =data= | headers to send with the request (=data= with key-value pairs, =slist= with colon-separated key-value pairs, or =string= with key:value pairs) on separate lines | No | - | @@ -25,6 +27,10 @@ the URL is specified in the promiser. *Note:* if no =file= attribute is provided the request will be sent to =/dev/null=. This is useful for something like a REST API endpoint where all you care about is an OK response (http code between 200 and 300) that results in the promise being =KEPT= +*Note:* The promiser can either be a URL of the request or, if the /url/ attribute is +used, it can be an arbitrary string. All attributes are optional if +the URL is specified in the promiser. + ** Result classes :PROPERTIES: :CUSTOM_ID: result-classes @@ -45,25 +51,11 @@ defined in case the response code is not an OK code. *The promiser is canonified in all the above classes.* -** Examples - -#+BEGIN_SRC cfengine3 -bundle agent __main__ -{ - http: - "https://cfengine.com/images/cfengine-logo.svg" - file => "/var/cfengine/cfengine-logo.svg", - if => not(fileexists("/var/cfengine/cfengine-logo.svg")); -} -#+END_SRC - ** Authors -This software was created by the team at [[https://northern.tech][Northern.tech AS]], with many +This software was created by the team at [[https://northern.tech][Northern.tech]], with many contributions from the community. Thanks everyone! -[[https://cfengine.com][CFEngine]] is sponsored by [[https://northern.tech][Northern.tech AS]] - ** Contribute Feel free to open pull requests to expand this documentation, add features or diff --git a/promise-types/http/http_promise_type.py b/promise-types/http/http_promise_type.py index 3f2cdea..735e5f0 100644 --- a/promise-types/http/http_promise_type.py +++ b/promise-types/http/http_promise_type.py @@ -20,10 +20,10 @@ def __init__(self, target): class HTTPPromiseModule(PromiseModule): - def __init__(self, name="http_promise_module", version="2.0.0", **kwargs): + def __init__(self, name="http_promise_module", version="2.0.1", **kwargs): super().__init__(name, version, **kwargs) - def validate_promise(self, promiser, attributes, meta): + def validate_promise(self, promiser, attributes, metadata): if "url" in attributes: url = attributes["url"] if type(url) != str: @@ -91,7 +91,7 @@ def target_fh(self, file_info): yield open(os.devnull, "wb") - def evaluate_promise(self, promiser, attributes, meta): + def evaluate_promise(self, promiser, attributes, metadata): url = attributes.get("url", promiser) method = attributes.get("method", "GET") headers = attributes.get("headers", dict()) diff --git a/promise-types/iptables/README.md b/promise-types/iptables/README.md index d149340..5c916c1 100644 --- a/promise-types/iptables/README.md +++ b/promise-types/iptables/README.md @@ -1,9 +1,4 @@ -# Iptables Promise Module - -## Synopsis -- *Name*: `iptables` -- *Version*: `0.1.2` -- *Description*: Manage network packet filter rules +Promise type for managing `iptables` firewall rules. ## Requirements - [`iptables`](https://manpages.ubuntu.com/manpages/precise/en/man8/iptables.8.html) (command line tool) @@ -221,9 +216,8 @@ All rules added by the iptables custom promise will have a comment signifing it ## Authors -This software was created by the team at [Northern.tech AS](https://northern.tech), with many contributions from the community. Thanks everyone! - -[CFEngine](https://cfengine.com) is sponsored by [Northern.tech AS](https://northern.tech) +This software was created by the team at [Northern.tech](https://northern.tech), with many contributions from the community. +Thanks everyone! ## Contribute diff --git a/promise-types/iptables/iptables.py b/promise-types/iptables/iptables.py index aab84c3..c8a90aa 100644 --- a/promise-types/iptables/iptables.py +++ b/promise-types/iptables/iptables.py @@ -159,7 +159,7 @@ def must_be_non_negative(v): self.add_attribute("rules", dict) self.add_attribute("executable", str, default="iptables") - def validate_promise(self, promiser: str, attributes: dict, meta: dict): + def validate_promise(self, promiser: str, attributes: dict, metadata: dict): command = attributes["command"] denied_attrs = self._collect_denied_attributes_of_command(command, attributes) @@ -182,7 +182,7 @@ def validate_promise(self, promiser: str, attributes: dict, meta: dict): if command != "flush" and attributes.get("chain") == "ALL": raise ValidationError("Chain 'ALL' is only available for command 'flush'") - def evaluate_promise(self, promiser: str, attributes: dict, meta: dict): + def evaluate_promise(self, promiser: str, attributes: Dict, metadata: Dict): safe_promiser = promiser.replace(",", "_") model = Model( @@ -281,7 +281,7 @@ def _iptables_policy(self, executable, table, chain, target): def _iptables_flush(self, executable, table, chain): args = [executable, "-t", table, "-F"] - if chain != 'ALL': + if chain != "ALL": args.append(chain) self._run(args) @@ -290,7 +290,7 @@ def _iptables_all_rules_of(self, executable, table, chain) -> List[str]: If chain is specified then only _one_ policy rule will be on top. """ args = [executable, "-t", table, "-S"] - if chain != 'ALL': + if chain != "ALL": args.append(chain) return self._run(args) diff --git a/promise-types/systemd/README.md b/promise-types/systemd/README.md index 6163b90..952c126 100644 --- a/promise-types/systemd/README.md +++ b/promise-types/systemd/README.md @@ -1,9 +1,4 @@ -# systemd promise module - -## Synopsis - -* *Name*: `systemd` -* *Description*: Create and manage services using systemd +The `systemd` promise type lets you create and manage services using systemd. ## Requirements @@ -98,9 +93,8 @@ bundle agent main ## Authors -This software was created by the team at [Northern.tech AS](https://northern.tech), with many contributions from the community. Thanks everyone! - -[CFEngine](https://cfengine.com) is sponsored by [Northern.tech AS](https://northern.tech) +This software was created by the team at [Northern.tech](https://northern.tech), with many contributions from the community. +Thanks everyone! ## Contribute diff --git a/promise-types/systemd/systemd.py b/promise-types/systemd/systemd.py index 6c5f422..aae1413 100644 --- a/promise-types/systemd/systemd.py +++ b/promise-types/systemd/systemd.py @@ -22,7 +22,7 @@ class SystemdPromiseTypeStates(Enum): class SystemdPromiseTypeModule(PromiseModule): def __init__(self, **kwargs): super(SystemdPromiseTypeModule, self).__init__( - "systemd_promise_module", "0.2.2", **kwargs + "systemd_promise_module", "0.2.3", **kwargs ) def state_must_be_valid(v): @@ -81,7 +81,7 @@ def prepare_promiser_and_attributes(self, promiser, attributes): return (safe_promiser, attributes) def evaluate_promise( - self, safe_promiser: str, attributes: Dict, meta: Dict + self, safe_promiser: str, attributes: Dict, metadata: Dict ) -> Tuple[str, List[str]]: model = self.create_attribute_object(safe_promiser, attributes) # get the status of the service