Skip to content

Commit 0c2447e

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 0c2447e

File tree

5 files changed

+199
-0
lines changed

5 files changed

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

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: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
body common control {
9+
bundlesequence => {"dependencies", "main"};
10+
}
11+
12+
body perms m_rxdirs( mode, rxdirs )
13+
{
14+
mode => "$(mode)";
15+
rxdirs => "$(rxdirs)";
16+
}
17+
18+
bundle agent dependencies {
19+
vars:
20+
21+
"options_str" string => '
22+
{
23+
"url.max_content": 1048576,
24+
"url.verbose": 0
25+
} ';
26+
"options" data => parsejson($(options_str));
27+
"url" string => "https://raw.githubusercontent.com/ansible-collections/community.general/9946f758af5fe0fe41f1cf7584d670ca1d6c2d52/plugins/modules/ini_file.py";
28+
"ansible_ini_file" data => url_get($(url), options);
29+
30+
classes:
31+
32+
"got_ini_file" expression => "$(ansible_ini_file[success])";
33+
34+
35+
files:
36+
37+
got_ini_file::
38+
"/tmp/cfe/ini_file.py"
39+
content => "$(ansible_ini_file[content])";
40+
41+
42+
got_ini_file::
43+
"/tmp/cfe/ini_file.py"
44+
perms => m_rxdirs( "0755", "true" );
45+
46+
reports:
47+
48+
got_ini_file::
49+
"$(this.bundle): Successfully retrieved the Ansible INI policy file";
50+
51+
!got_ini_file::
52+
"$(this.bundle): failed to get the Ansible INI policy file $(ansible_ini_file[error_message])";
53+
54+
}
55+
56+
bundle agent main
57+
{
58+
59+
meta:
60+
"bundle_version" string => "0.0.1";
61+
"promise_type" string => "ini";
62+
63+
ini:
64+
"/home/olepor/cfengine/modules/promise-types/ini/test.toml"
65+
module_path => "/tmp/cfe/ini_file.py",
66+
section => "foo",
67+
option => "bar",
68+
value => "baz";
69+
}

promise-types/ini/ini.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"""The ultimate CFEngine custom promise module"""
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+
# AnsiballINIModule specific (the path from the policy)
15+
self.add_attribute("module_path", str)
16+
17+
self.add_attribute("path", str, default_to_promiser=True)
18+
19+
def validate_attributes(self, promiser, attributes, meta):
20+
# Just pass the attributes on transparently to Ansible INI The Ansible
21+
# module will report if the missing parameters are not an Ansible attributes
22+
if not attributes.get("module_path"):
23+
self.log_error("'module_path' is a required promise attribute")
24+
return (result.NOT_KEPT, [])
25+
26+
return True
27+
28+
def validate_promise(self, promiser: str, attributes: dict, meta: dict) -> None:
29+
self.log_debug(
30+
"Validating the ansible ini promise: %s %s %s"
31+
% (promiser, attributes, meta)
32+
)
33+
if not meta.get("promise_type"):
34+
raise ValidationError("Promise type not specified")
35+
36+
assert meta.get("promise_type") == "ini"
37+
38+
def evaluate_promise(self, promiser: str, attributes: dict, meta: dict):
39+
self.log_debug(
40+
"Evaluating the ansible ini promise %s, %s, %s"
41+
% (promiser, attributes, meta)
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+
proc = subprocess.run(
52+
[
53+
"python",
54+
module_path,
55+
],
56+
input=json.dumps({"ANSIBLE_MODULE_ARGS": attributes}).encode("utf-8"),
57+
stdout=subprocess.PIPE,
58+
stderr=subprocess.PIPE,
59+
)
60+
61+
if not proc:
62+
self.log_error("Failed to run the ansible module")
63+
return (
64+
Result.NOT_KEPT,
65+
[],
66+
)
67+
68+
if proc.returncode != 0:
69+
self.log_error("Failed to run the ansible module")
70+
self.log_error("Ansible INI module returned(stdout): %s" % proc.stdout)
71+
self.log_error("Ansible INI module returned(stderr): %s" % proc.stderr)
72+
return (
73+
Result.NOT_KEPT,
74+
[],
75+
)
76+
77+
self.log_debug("Received output: %s (stdout)" % proc.stdout)
78+
self.log_debug("Received output (stderr): %s" % proc.stderr)
79+
return (
80+
Result.KEPT,
81+
[],
82+
)
83+
84+
85+
if __name__ == "__main__":
86+
AnsiballINIModule().start()

0 commit comments

Comments
 (0)