Skip to content

Commit c43285f

Browse files
committed
Ansible INI custom promise module wrapper
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 <ole.orhagen@northern.tech>
1 parent 9718a0b commit c43285f

File tree

5 files changed

+170
-0
lines changed

5 files changed

+170
-0
lines changed

cfbs.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,15 @@
180180
"append enable.cf services/init.cf"
181181
]
182182
},
183+
"promise-type-ini": {
184+
"description": "A Custom CFEngine promise module, installing, and using the Ansible INI module to provide an INI promise type for INI files",
185+
"subdirectory": "promise-types/ini",
186+
"dependencies": ["library-for-promise-types-in-python"],
187+
"steps": [
188+
"copy ini.py modules/promises/",
189+
"append enable.cf services/init.cf"
190+
]
191+
},
183192
"uninstall-packages": {
184193
"description": "Allows you to specify a list of packages you want uninstalled on your hosts.",
185194
"subdirectory": "security/uninstall-packages",

promise-types/ini/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Ansible INI promise type
2+
3+
## Synopsis
4+
5+
* *Name*: `Ansible INI - Custom Promise Module`
6+
* *Version*: `0.0.1`
7+
* *Description*: Manage configuration files through the Ansible INI module in CFEngine.
8+
9+
## Requirements
10+
11+
* Python installed on the system
12+
* `ansible` pip package
13+
* Correct path to the `ini_file.py` in the custom promise module
14+
15+
## Attributes
16+
17+
See [anible_ini module](https://docs.ansible.com/ansible/latest/collections/community/general/ini_file_module.html).
18+
19+
## Example
20+
21+
```cfengine3
22+
bundle agent main
23+
{
24+
ini:
25+
"/path/to/file.ini"
26+
section => "foo",
27+
option => "bar",
28+
value => "baz";
29+
}
30+
```

promise-types/ini/enable.cf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
promise agent ini
2+
# @brief Define ini promise type
3+
{
4+
path => "$(sys.workdir)/modules/promises/ini.py";
5+
interpreter => "/usr/bin/python3";
6+
}

promise-types/ini/example.cf

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
promise agent ini
2+
# @brief Define ini promise type
3+
{
4+
path => "$(sys.workdir)/modules/promises/ini.py";
5+
interpreter => "/usr/bin/python3";
6+
}
7+
8+
bundle agent main
9+
{
10+
11+
meta:
12+
"bundle_version" string => "0.0.1";
13+
"promise_type" string => "ini";
14+
15+
ini:
16+
"/tmp/ini/test.ini"
17+
section => "foo",
18+
option => "bar",
19+
value => "baz";
20+
}

promise-types/ini/ini.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""A CFEngine custom promise module for INI files"""
2+
3+
import json
4+
import subprocess
5+
import sys
6+
7+
from cfengine import PromiseModule, ValidationError, Result
8+
9+
10+
class AnsiballINIModule(PromiseModule):
11+
def __init__(self):
12+
super().__init__("ansible_ini_promise_module", "0.0.1")
13+
14+
self.add_attribute("path", str, default_to_promiser=True)
15+
16+
def validate_attributes(self, promiser, attributes, meta):
17+
# Just pass the attributes on transparently to Ansible INI The Ansible
18+
# module will report if the missing parameters are not in Ansible attributes
19+
20+
return True
21+
22+
def validate_promise(self, promiser: str, attributes: dict, meta: dict) -> None:
23+
self.log_error(
24+
"Validating the ansible ini promise: %s %s %s"
25+
% (promiser, attributes, meta)
26+
)
27+
if not meta.get("promise_type"):
28+
raise ValidationError("Promise type not specified")
29+
30+
assert meta.get("promise_type") == "ini"
31+
32+
def evaluate_promise(self, promiser: str, attributes: dict, meta: dict):
33+
self.log_error(
34+
"Evaluating the ansible ini promise %s, %s, %s"
35+
% (promiser, attributes, meta)
36+
)
37+
38+
if "module_path" not in attributes:
39+
attributes.setdefault(
40+
"module_path",
41+
"/tmp/ini_file.py",
42+
)
43+
44+
# NOTE: INI module specific - should not be passed on to Ansible
45+
module_path = attributes["module_path"]
46+
del attributes["module_path"]
47+
48+
# NOTE - needed because 'default_to_promiser' is not respected
49+
attributes.setdefault("path", promiser)
50+
51+
self.log_error(
52+
"Evaluating the ansible ini promise %s, %s, %s"
53+
% (promiser, attributes, meta)
54+
)
55+
56+
proc = subprocess.run(
57+
[
58+
"python",
59+
module_path,
60+
],
61+
input=json.dumps({"ANSIBLE_MODULE_ARGS": attributes}).encode("utf-8"),
62+
stdout=subprocess.PIPE,
63+
stderr=subprocess.PIPE,
64+
)
65+
66+
if not proc:
67+
self.log_error("Failed to run the ansible module")
68+
return (
69+
Result.NOT_KEPT,
70+
[],
71+
)
72+
73+
if proc.returncode != 0:
74+
self.log_error("Failed to run the ansible module")
75+
self.log_error("Ansible INI module returned(stdout): %s" % proc.stdout)
76+
self.log_error("Ansible INI module returned(stderr): %s" % proc.stderr)
77+
return (
78+
Result.NOT_KEPT,
79+
[],
80+
)
81+
82+
self.log_debug("Received output: %s (stdout)" % proc.stdout)
83+
self.log_debug("Received output: (stderr): %s" % proc.stderr)
84+
85+
try:
86+
d = json.loads(proc.stdout.decode("UTF-8").strip())
87+
if d.get("changed", False):
88+
self.log_info(
89+
"Edited content of '%s' (%s)" % (promiser, d.get("msg", ""))
90+
)
91+
except Exception as e:
92+
self.log_error(
93+
"Failed to decode the JSON returned from the Ansible INI module. Error: %s"
94+
% e
95+
)
96+
return (Result.NOT_KEPT, [])
97+
98+
return (
99+
Result.KEPT,
100+
[],
101+
)
102+
103+
104+
if __name__ == "__main__":
105+
AnsiballINIModule().start()

0 commit comments

Comments
 (0)