Skip to content

Commit

Permalink
RFC: Ansible INI promise module wrapper
Browse files Browse the repository at this point in the history
Signed-off-by: Ole Petter <[email protected]>
  • Loading branch information
oleorhagen committed Jan 12, 2024
1 parent 9718a0b commit f287922
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 0 deletions.
28 changes: 28 additions & 0 deletions promise-types/ini/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 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

* TODO - TBD

## 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 => "fav",
value => "lemonade";
}
```
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)/inputs/modules/promises/ini.py";
interpreter => "/usr/bin/python3";
}
79 changes: 79 additions & 0 deletions promise-types/ini/example.cf
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
promise agent ini
# @brief Define ini promise type
{
path => "$(sys.workdir)/inputs/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( "0555", "true" );

# TODO - How to make sure that Python is installed
# packages:
# "python"
# policy => "present",
# package_module => generic,
# comment => "Install python";

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:
"tags" slist => { "autorun" };
"bundle_version" string => "0.0.1";
"promise_type" string => "ini";

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

import glob
import json
import os
import shutil
import subprocess
import sys
import tempfile

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, default="/tmp/cfe/ini_file.py")

self.add_attribute("path", str, default_to_promiser=True)
self.add_attribute("allow_no_value", bool, default=False)
self.add_attribute("attributes", str)
self.add_attribute("backup", bool, default=False)
self.add_attribute("create", str, default="yes")
self.add_attribute("group", str)
self.add_attribute("mode", str)
self.add_attribute("no_extra_spaces", str, default="yes")
self.add_attribute("option", str)
self.add_attribute("owner", str)
self.add_attribute("section", str, required=True)
# TODO - Add seLinux attrs
# self.add_attribute("selevel", str, default="sO")
# self.add_attribute("serole")

self.add_attribute("state", str, default="present")
self.add_attribute("unsafe_writes", bool, default=False)
self.add_attribute("value", str)

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")

# Not an Ansible attribute
if not attributes.get("module_path"):
self.log_error("'module_path' is a required promise attribute")
return (result.NOT_KEPT, [])

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

# 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: %s" % proc.stdout)
self.log_error("Ansible INI module returned: %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)
return (
Result.KEPT,
[],
)


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

0 comments on commit f287922

Please sign in to comment.