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