Skip to content

Commit

Permalink
Ansible INI custom promise module wrapper
Browse files Browse the repository at this point in the history
This adds a custom promise module for wrapping the existing Ansible INI module.

The module simply passes on values from the custom 'ini' promise type, to the
Ansible module.

With the example follows an example policy for downloading the required Ansible
INI module.

Signed-off-by: Ole Petter <[email protected]>
  • Loading branch information
oleorhagen committed Jan 16, 2024
1 parent 9718a0b commit 565a484
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 0 deletions.
9 changes: 9 additions & 0 deletions cfbs.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,15 @@
"append enable.cf services/init.cf"
]
},
"promise-type-ini": {
"description": "A Custom CFEngine promise module, installing, and using the Ansible INI module to provide an INI promise type for INI files",
"subdirectory": "promise-types/ini",
"dependencies": ["library-for-promise-types-in-python"],
"steps": [
"copy ini.py modules/promises/",
"append enable.cf services/init.cf"
]
},
"uninstall-packages": {
"description": "Allows you to specify a list of packages you want uninstalled on your hosts.",
"subdirectory": "security/uninstall-packages",
Expand Down
29 changes: 29 additions & 0 deletions promise-types/ini/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Ansible INI promise type

## Synopsis

* *Name*: `Ansible INI - Custom Promise Module`
* *Version*: `0.0.1`
* *Description*: Manage TOML configuration files through the Ansible INI module in CFEngine.

## Requirements

* Python installed on the system
* `ansible-core` pip package

## Attributes

See [anible_ini module](https://docs.ansible.com/ansible/latest/collections/community/general/ini_file_module.html).

## Example

```cfengine3
bundle agent main
{
ini:
"/path/to/file.toml"
section => "foo",
option => "bar",
value => "baz";
}
```
6 changes: 6 additions & 0 deletions promise-types/ini/enable.cf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
promise agent ini
# @brief Define ini promise type
{
path => "$(sys.workdir)/modules/promises/ini.py";
interpreter => "/usr/bin/python3";
}
69 changes: 69 additions & 0 deletions promise-types/ini/example.cf
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
promise agent ini
# @brief Define ini promise type
{
path => "$(sys.workdir)/modules/promises/ini.py";
interpreter => "/usr/bin/python3";
}

body common control {
bundlesequence => {"dependencies", "main"};
}

body perms m_rxdirs( mode, rxdirs )
{
mode => "$(mode)";
rxdirs => "$(rxdirs)";
}

bundle agent dependencies {
vars:

"options_str" string => '
{
"url.max_content": 1048576,
"url.verbose": 0
} ';
"options" data => parsejson($(options_str));
"url" string => "https://raw.githubusercontent.com/ansible-collections/community.general/9946f758af5fe0fe41f1cf7584d670ca1d6c2d52/plugins/modules/ini_file.py";
"ansible_ini_file" data => url_get($(url), options);

classes:

"got_ini_file" expression => "$(ansible_ini_file[success])";


files:

got_ini_file::
"/tmp/cfe/ini_file.py"
content => "$(ansible_ini_file[content])";


got_ini_file::
"/tmp/cfe/ini_file.py"
perms => m_rxdirs( "0755", "true" );

reports:

got_ini_file::
"$(this.bundle): Successfully retrieved the Ansible INI policy file";

!got_ini_file::
"$(this.bundle): failed to get the Ansible INI policy file $(ansible_ini_file[error_message])";

}

bundle agent main
{

meta:
"bundle_version" string => "0.0.1";
"promise_type" string => "ini";

ini:
"/tmp/ini/test.toml"
module_path => "/tmp/cfe/ini_file.py",
section => "foo",
option => "bar",
value => "baz";
}
98 changes: 98 additions & 0 deletions promise-types/ini/ini.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""The ultimate CFEngine custom promise module"""

import json
import subprocess
import sys

from cfengine import PromiseModule, ValidationError, Result


class AnsiballINIModule(PromiseModule):
def __init__(self):
super().__init__("ansible_ini_promise_module", "0.0.1")

# AnsiballINIModule specific (the path from the policy)
self.add_attribute("module_path", str)

self.add_attribute("path", str, default_to_promiser=True)

def validate_attributes(self, promiser, attributes, meta):
# Just pass the attributes on transparently to Ansible INI The Ansible
# module will report if the missing parameters are not an Ansible attributes
if not attributes.get("module_path"):
self.log_error("'module_path' is a required promise attribute")
return (result.NOT_KEPT, [])

return True

def validate_promise(self, promiser: str, attributes: dict, meta: dict) -> None:
self.log_debug(
"Validating the ansible ini promise: %s %s %s"
% (promiser, attributes, meta)
)
if not meta.get("promise_type"):
raise ValidationError("Promise type not specified")

assert meta.get("promise_type") == "ini"

def evaluate_promise(self, promiser: str, attributes: dict, meta: dict):
self.log_debug(
"Evaluating the ansible ini promise %s, %s, %s"
% (promiser, attributes, meta)
)

# NOTE: INI module specific - should not be passed on to Ansible
module_path = attributes["module_path"]
del attributes["module_path"]

# NOTE - needed because 'default_to_promiser' is not respected
attributes.setdefault("path", promiser)

proc = subprocess.run(
[
"python",
module_path,
],
input=json.dumps({"ANSIBLE_MODULE_ARGS": attributes}).encode("utf-8"),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

if not proc:
self.log_error("Failed to run the ansible module")
return (
Result.NOT_KEPT,
[],
)

if proc.returncode != 0:
self.log_error("Failed to run the ansible module")
self.log_error("Ansible INI module returned(stdout): %s" % proc.stdout)
self.log_error("Ansible INI module returned(stderr): %s" % proc.stderr)
return (
Result.NOT_KEPT,
[],
)

self.log_debug("Received output: %s (stdout)" % proc.stdout)
self.log_debug("Received output: (stderr): %s" % proc.stderr)

try:
d = json.loads(proc.stdout.decode("UTF-8").strip())
if d.get("changed", False):
self.log_info("Ansible INI returned msg: %s" % d.get("msg", ""))
except Exception as e:
self.log_error(
"Failed to decode the JSON returned from the Ansible INI module. Error: %s"
% e
)
return (Result.NOT_KEPT, [])

return (
Result.KEPT,
[],
)


if __name__ == "__main__":
AnsiballINIModule().start()

0 comments on commit 565a484

Please sign in to comment.