phpansibleansible-2.xansible-inventory

Ansible not executing PHP dynamic inventory file


I am having issues with Ansible not executing a PHP dynamic inventory file that I have written. To troubleshoot the issue I have created a test.json inventory file to verify my syntax is correct.

cat test.json
{
  "aruba_cx": {
    "children": {
      "hardware_6000": {
        "hosts": {
          "sw-01.domain.uk": null
        }
      },
      "hardware_6200": {
        "hosts": {
          "sw-02.net.domain.uk": null
        }
      }
    }
  },
  "aruba_aos": {
    "children": {
      "hardware_2930": {
        "hosts": {
          "sw-03.domain.uk": null
        }
      }
    }
  }
}

This is accepted by Ansible.

ansible-inventory -i 'test.json' --list
{
    "_meta": {
        "hostvars": {}
    },
    "all": {
        "children": [
            "ungrouped",
            "aruba_cx",
            "aruba_aos"
        ]
    },
    "aruba_aos": {
        "children": [
            "hardware_2930"
        ]
    },
    "aruba_cx": {
        "children": [
            "hardware_6000",
            "hardware_6200"
        ]
    },
    "hardware_2930": {
        "hosts": [
            "sw-03.domain.uk"
        ]
    },
    "hardware_6000": {
        "hosts": [
            "sw-01.domain.uk"
        ]
    },
    "hardware_6200": {
        "hosts": [
            "sw-02.domain.uk"
        ]
    }
}

However, when I was the same JSON via a PHP executable script Ansible doesn't seem to execute the file before parsing it.

cat test.php
#!/usr/bin/env php
<?php

// Require iMC Device List
echo require_once "test.json";
ansible-inventory -i './test.php' --list -vvvv
ansible-inventory [core 2.15.12]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/william/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/william/.local/lib/python3.9/site-packages/ansible
  ansible collection location = /home/william/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/william/.local/bin/ansible-inventory
  python version = 3.9.2 (default, Feb 28 2021, 17:03:44) [GCC 10.2.1 20210110] (/usr/bin/python3)
  jinja version = 3.1.4
  libyaml = True
Using /etc/ansible/ansible.cfg as config file
setting up inventory plugins
Loading collection ansible.builtin from
[WARNING]:  * Failed to parse /home/william/ansible-networks/t.php with script plugin: failed to parse executable inventory script results from /home/william/ansible-networks/t.php: Extra data: line 25 column 2 (char
449). Extra data: line 25 column 2 (char 449)
  File "/home/william/.local/lib/python3.9/site-packages/ansible/inventory/manager.py", line 293, in parse_source
    plugin.parse(self._inventory, self._loader, source, cache=cache)
  File "/home/william/.local/lib/python3.9/site-packages/ansible/plugins/inventory/script.py", line 150, in parse
    raise AnsibleParserError(to_native(e))
[WARNING]: Unable to parse /home/william/ansible-networks/t.php as an inventory source
[WARNING]: No inventory was parsed, only implicit localhost is available
{
    "_meta": {
        "hostvars": {}
    },
    "all": {
        "children": [
            "ungrouped"
        ]
    }
}

Ansible version: 2.15.12

PHP version: 7.4.33

The shebang on this system is #!/usr/bin/env php and has been tested using ./test.php which executes successfully.

The following permissions are set -rwxrwx--- 1 william it-dept 82 Aug 13 12:03 test.php

I have tested adding the enable_plugins = script into my ansible.cfg

cat /etc/ansible/ansible.cfg
[defaults]
forks=50
deprecation_warnings=False
transport = ssh
host_key_checking = False
pipelining = True
[ssh_connection]
ssh_args = -F /etc/ssh_config -o ControlMaster=auto -o ControlPersist=60s
[inventory]
#enable_plugins = script

I'm running out of ideas, any pointers on this one would be smashing.


Solution

  • Ansible has absolutely no troubles to execute your script and get its output. The issue pops up when the inventory script plugin tries to parse the result. The next errors are simple try with other plugins which don't work either.

    You actually have two problems:

    1. You are using echo in you php file to write out the result of a function/expression. The result of require_once "test.json" is 1. So what you actually get is the content of your json file (as gotten by the require_once instruction) followed by 1 at the end. That extra character confuses the script plugin parser.
    2. Although your json file can perfectly represent a valid static inventory, it is not the expected output for an inventory script

    Fixing the first problem is trivial. Just change your php file to:

    #!/usr/bin/env php
    <?php
    
    // Require iMC Device List
    require_once "test.json";
    

    To fix the second, here is a rearranged output as expected by the script plugin as explained in the developper's guide. Note the presence of a _meta section with an empty hostvars entry to make sure ansible only calls your current script with the --list parameter (which will work by default as is) and never with the --host <hostname> parameter that would need to be implemented in such case.

    {
      "aruba_cx": {
        "children": ["hardware_6000", "hardware_6200"]
      },
      "hardware_6000": {
        "hosts": ["sw-01.domain.uk"]
      },
      "hardware_6200": {
        "hosts": ["sw-02.net.domain.uk"]
      },
      "aruba_aos": {
        "children": ["hardware_2930"]
      },
      "hardware_2930": {
        "hosts": ["sw-03.domain.uk"]
      },
      "_meta": {
          "hostvars": {}
      }
    }
    

    After those fix, it works as expected:

    $ ansible-inventory -i test.php --list
    {
        "_meta": {
            "hostvars": {}
        },
        "all": {
            "children": [
                "ungrouped",
                "aruba_cx",
                "aruba_aos"
            ]
        },
        "aruba_aos": {
            "children": [
                "hardware_2930"
            ]
        },
        "aruba_cx": {
            "children": [
                "hardware_6000",
                "hardware_6200"
            ]
        },
        "hardware_2930": {
            "hosts": [
                "sw-03.domain.uk"
            ]
        },
        "hardware_6000": {
            "hosts": [
                "sw-01.domain.uk"
            ]
        },
        "hardware_6200": {
            "hosts": [
                "sw-02.net.domain.uk"
            ]
        }
    }