Skip to content

Instantly share code, notes, and snippets.

@tboerger
Last active March 16, 2023 15:48
Show Gist options
  • Save tboerger/c37e95f063f604951efc4ece43bf2246 to your computer and use it in GitHub Desktop.
Save tboerger/c37e95f063f604951efc4ece43bf2246 to your computer and use it in GitHub Desktop.

Revisions

  1. tboerger revised this gist May 13, 2020. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions inventory.py
    Original file line number Diff line number Diff line change
    @@ -196,6 +196,7 @@ def call_update_cache(self):

    for vapp in self.gather_vapp_list():
    vapp_name = vapp.get('name')
    self.inventory['server']['children'].append(vapp_name)

    if not vapp_name in self.inventory.keys():
    self.inventory[vapp_name] = {
  2. tboerger revised this gist May 13, 2020. 2 changed files with 213 additions and 136 deletions.
    329 changes: 203 additions & 126 deletions inventory.py
    Original file line number Diff line number Diff line change
    @@ -3,168 +3,245 @@
    import json
    import requests
    import os
    import argparse
    import xml.etree.cElementTree as ET
    from time import time

    GLOBALS = {
    'ansible_become': 'true',
    'ansible_python_interpreter': '/usr/bin/python3',
    'ansible_ssh_common_args': '-o StrictHostKeyChecking=no -o ProxyCommand="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -W %h:%p [email protected]"'
    }

    OVERRIDE = {
    'bastion-01': {
    'ansible_host': '192.168.1.1',
    'ansible_ssh_common_args': '-o StrictHostKeyChecking=no'
    },
    'bastion-02': {
    'ansible_host': '192.168.1.2',
    'ansible_ssh_common_args': '-o StrictHostKeyChecking=no'
    }
    }

    #
    # Just provide the following environment variables:
    # VCD_URL - https://vcloud.example.com/api
    # VCD_USER - username
    # VCD_PASSWORD - p455w0rd
    # VCD_ORG - octocat
    #

    def main():
    config = {
    'base_url': '',
    'username': '',
    'password': '',
    'org': '',
    'headers': {
    'Accept': 'application/*+xml;version=30.0'
    },
    # globals gets merged into all hostvars for every host
    'globals': {
    'ansible_become': 'true',
    'ansible_python_interpreter': '/usr/bin/python3',
    'ansible_ssh_common_args': '-o StrictHostKeyChecking=no -o ProxyCommand="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -W %h:%p [email protected]"'
    },
    # override can be used to override specific hostvars
    'override': {
    'bastion-01': {
    'ansible_host': '192.168.1.1',
    'ansible_ssh_common_args': '-o StrictHostKeyChecking=no'
    class VcdInventory(object):
    def _empty_inventory(self):
    return {
    'server': {
    'hosts': []
    },
    'bastion-02': {
    'ansible_host': '192.168.1.2',
    'ansible_ssh_common_args': '-o StrictHostKeyChecking=no'
    '_meta': {
    'hostvars': {}
    }
    }
    }

    config['base_url'] = os.environ.get('VCD_URL', '')
    def __init__(self):
    self.inventory = self._empty_inventory()

    self.credentials = {
    'base_url': '',
    'username': '',
    'password': '',
    'org': '',
    'headers': {
    'Accept': 'application/*+xml;version=30.0'
    },
    }

    self.parse_cli_args()
    self.read_credentials()
    self.configure_cache()

    if self.args.refresh_cache:
    self.call_update_cache()
    elif not self.is_cache_valid():
    self.call_update_cache()

    if self.args.host:
    data_to_print = self.host_information()
    elif self.args.list:
    if self.inventory == self._empty_inventory():
    data_to_print = self.inventory_from_cache()
    else:
    data_to_print = self.json_format_dict(self.inventory, True)

    print(data_to_print)

    def parse_cli_args(self):
    parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on vCD')

    parser.add_argument(
    '--list',
    action='store_true',
    default=True,
    help='List instances, used as default action as well'
    )

    parser.add_argument(
    '--host',
    action='store',
    default=False,
    help='Get all the variables about a specific instance'
    )

    parser.add_argument(
    '--refresh-cache',
    action='store_true',
    default=False,
    help='Force refresh of cache by making API requests'
    )

    self.args = parser.parse_args()

    def read_credentials(self):
    self.credentials['base_url'] = os.environ.get('VCD_URL', '')

    if not self.credentials['base_url'].strip():
    print('Missing VCD_URL environment variable!')
    exit(1)

    self.credentials['username'] = os.environ.get('VCD_USER', '')

    if not self.credentials['username'].strip():
    print('Missing VCD_USER environment variable!')
    exit(1)

    if not config['base_url'].strip():
    print('Missing VCD_URL environment variable!')
    exit(1)
    self.credentials['password'] = os.environ.get('VCD_PASSWORD', '')

    config['username'] = os.environ.get('VCD_USER', '')
    if not self.credentials['password'].strip():
    print('Missing VCD_PASSWORD environment variable!')
    exit(1)

    if not config['username'].strip():
    print('Missing VCD_USER environment variable!')
    exit(1)
    self.credentials['org'] = os.environ.get('VCD_ORG', '')

    config['password'] = os.environ.get('VCD_PASSWORD', '')
    if not self.credentials['org'].strip():
    print('Missing VCD_ORG environment variable!')
    exit(1)

    if not config['password'].strip():
    print('Missing VCD_PASSWORD environment variable!')
    exit(1)
    def configure_cache(self):
    cache_dir = os.path.expanduser('~/.vcd/cache')

    config['org'] = os.environ.get('VCD_ORG', '')
    if not os.path.exists(cache_dir):
    os.makedirs(cache_dir)

    if not config['org'].strip():
    print('Missing VCD_ORG environment variable!')
    exit(1)
    cache_name = self.credentials.get('org')
    cache_name += '-' + self.credentials.get('username')

    login(config)
    self.cache_path_file = os.path.join(cache_dir, '%s.cache' % cache_name)
    self.cache_max_age = 900

    available = {}
    server = []
    def is_cache_valid(self):
    if os.path.isfile(self.cache_path_file):
    mod_time = os.path.getmtime(self.cache_path_file)
    current_time = time()

    root = {
    'server': {
    'hosts': server
    },
    '_meta': {
    'hostvars': available
    return (mod_time + self.cache_max_age) > current_time

    return False

    def write_to_cache(self, data, filename):
    with open(filename, 'w') as f:
    f.write(self.json_format_dict(data, True))

    def gather_vapp_list(self):
    url = self.credentials['base_url'] + '/vApps/query'

    return self.extract_from_tree(
    url
    ).findall(
    '{http://www.vmware.com/vcloud/v1.5}VAppRecord'
    )

    def gather_hosts_from(self, href):
    return self.extract_from_tree(
    href
    ).iter(
    '{http://www.vmware.com/vcloud/v1.5}Vm'
    )

    def extract_from_tree(self, url):
    return ET.fromstring(
    requests.get(
    url,
    headers=self.credentials['headers']
    ).content
    )

    def merge_available_attrs(self, host):
    result = {
    'ansible_host': self.search_within_attrs(
    host,
    '{http://www.vmware.com/vcloud/v1.5}IpAddress',
    True,
    ''
    )
    }
    }

    for vapp in vapps(config):
    for host in hosts(config, vapp.get('href')):
    host_name = host.get('name')
    result.update(GLOBALS)

    if host.get('name') in OVERRIDE.keys():
    result.update(OVERRIDE[host.get('name')])

    server.append(host.get('name'))
    available[host_name] = fillup(config, host)
    return result

    groups(root, vapp, host)
    def search_within_attrs(self, root, tag, text, attr):
    for elem in root.iter(tag):
    if text:
    return elem.text
    else:
    return elem.get(attr)

    print(json.dumps(root))
    def call_update_cache(self):
    self.authenticate_to_api()

    def login(config):
    url = config['base_url'] + '/sessions'
    for vapp in self.gather_vapp_list():
    vapp_name = vapp.get('name')

    session = requests.post(
    url,
    headers=config['headers'],
    auth=(config['username'] + "@" + config['org'], config['password'])
    )
    if not vapp_name in self.inventory.keys():
    self.inventory[vapp_name] = {
    'hosts': []
    }

    config['headers']['x-vcloud-authorization'] = session.headers['x-vcloud-authorization']
    for host in self.gather_hosts_from(vapp.get('href')):
    host_name = host.get('name')

    def vapps(config):
    url = config['base_url'] + '/vApps/query'
    self.inventory['server']['hosts'].append(host.get('name'))
    self.inventory[vapp_name]['hosts'].append(host.get('name'))
    self.inventory['_meta']['hostvars'][host_name] = self.merge_available_attrs(host)

    return tree(
    url,
    config['headers'],
    ).findall(
    '{http://www.vmware.com/vcloud/v1.5}VAppRecord'
    )
    self.write_to_cache(self.inventory, self.cache_path_file)

    def hosts(config, href):
    return tree(
    href,
    config['headers'],
    ).iter(
    '{http://www.vmware.com/vcloud/v1.5}Vm'
    )
    def authenticate_to_api(self):
    url = self.credentials['base_url'] + '/sessions'

    def tree(url, headers):
    return ET.fromstring(
    requests.get(
    session = requests.post(
    url,
    headers=headers
    ).content
    )

    def fillup(config, host):
    result = {
    'ansible_host': search(
    host,
    '{http://www.vmware.com/vcloud/v1.5}IpAddress',
    True,
    ''
    headers=self.credentials['headers'],
    auth=(self.credentials['username'] + '@' + self.credentials['org'], self.credentials['password'])
    )
    }

    result.update(config['globals'])

    if host.get('name') in config['override'].keys():
    result.update(config['override'][host.get('name')])
    self.credentials['headers']['x-vcloud-authorization'] = session.headers['x-vcloud-authorization']

    return result
    def inventory_from_cache(self):
    with open(self.cache_path_file, 'r') as f:
    return f.read()

    def search(root, tag, text, attr):
    for elem in root.iter(tag):
    if text:
    return elem.text
    def json_format_dict(self, data, pretty=False):
    if pretty:
    return json.dumps(data, sort_keys=True, indent=2)
    else:
    return elem.get(attr)
    return json.dumps(data)

    def groups(root, vapp, host):
    name = vapp.get('name')
    def host_information(self):
    self.inventory = json.loads(self.inventory_from_cache())

    if not name in root:
    root[name] = {
    'hosts': []
    }
    if self.args.host not in self.inventory['_meta']['hostvars'].keys():
    self.call_update_cache()

    if self.args.host not in self.inventory['_meta']['hostvars'].keys():
    return self.json_format_dict({}, True)

    root[name]['hosts'].append(search(
    host,
    '{http://www.vmware.com/vcloud/v1.5}Vm',
    False,
    "name"
    ))
    return self.json_format_dict(self.inventory['_meta']['hostvars'][self.args.host], True)

    if __name__ == '__main__':
    main()
    VcdInventory()
    20 changes: 10 additions & 10 deletions output.json
    Original file line number Diff line number Diff line change
    @@ -1,14 +1,4 @@
    {
    "server": {
    "hosts": [
    "bastion-01",
    "bastion-02",
    "haproxy-01",
    "haproxy-02",
    "mariadb-01",
    "mariadb-02"
    ]
    },
    "_meta": {
    "hostvars": {
    "bastion-01": {
    @@ -66,5 +56,15 @@
    "mariadb-01",
    "mariadb-02"
    ]
    },
    "server": {
    "hosts": [
    "bastion-01",
    "bastion-02",
    "haproxy-01",
    "haproxy-02",
    "mariadb-01",
    "mariadb-02"
    ]
    }
    }
  3. tboerger created this gist May 13, 2020.
    170 changes: 170 additions & 0 deletions inventory.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,170 @@
    #!/usr/bin/env python3

    import json
    import requests
    import os
    import xml.etree.cElementTree as ET

    #
    # Just provide the following environment variables:
    # VCD_URL - https://vcloud.example.com/api
    # VCD_USER - username
    # VCD_PASSWORD - p455w0rd
    # VCD_ORG - octocat
    #

    def main():
    config = {
    'base_url': '',
    'username': '',
    'password': '',
    'org': '',
    'headers': {
    'Accept': 'application/*+xml;version=30.0'
    },
    # globals gets merged into all hostvars for every host
    'globals': {
    'ansible_become': 'true',
    'ansible_python_interpreter': '/usr/bin/python3',
    'ansible_ssh_common_args': '-o StrictHostKeyChecking=no -o ProxyCommand="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -W %h:%p [email protected]"'
    },
    # override can be used to override specific hostvars
    'override': {
    'bastion-01': {
    'ansible_host': '192.168.1.1',
    'ansible_ssh_common_args': '-o StrictHostKeyChecking=no'
    },
    'bastion-02': {
    'ansible_host': '192.168.1.2',
    'ansible_ssh_common_args': '-o StrictHostKeyChecking=no'
    }
    }
    }

    config['base_url'] = os.environ.get('VCD_URL', '')

    if not config['base_url'].strip():
    print('Missing VCD_URL environment variable!')
    exit(1)

    config['username'] = os.environ.get('VCD_USER', '')

    if not config['username'].strip():
    print('Missing VCD_USER environment variable!')
    exit(1)

    config['password'] = os.environ.get('VCD_PASSWORD', '')

    if not config['password'].strip():
    print('Missing VCD_PASSWORD environment variable!')
    exit(1)

    config['org'] = os.environ.get('VCD_ORG', '')

    if not config['org'].strip():
    print('Missing VCD_ORG environment variable!')
    exit(1)

    login(config)

    available = {}
    server = []

    root = {
    'server': {
    'hosts': server
    },
    '_meta': {
    'hostvars': available
    }
    }

    for vapp in vapps(config):
    for host in hosts(config, vapp.get('href')):
    host_name = host.get('name')

    server.append(host.get('name'))
    available[host_name] = fillup(config, host)

    groups(root, vapp, host)

    print(json.dumps(root))

    def login(config):
    url = config['base_url'] + '/sessions'

    session = requests.post(
    url,
    headers=config['headers'],
    auth=(config['username'] + "@" + config['org'], config['password'])
    )

    config['headers']['x-vcloud-authorization'] = session.headers['x-vcloud-authorization']

    def vapps(config):
    url = config['base_url'] + '/vApps/query'

    return tree(
    url,
    config['headers'],
    ).findall(
    '{http://www.vmware.com/vcloud/v1.5}VAppRecord'
    )

    def hosts(config, href):
    return tree(
    href,
    config['headers'],
    ).iter(
    '{http://www.vmware.com/vcloud/v1.5}Vm'
    )

    def tree(url, headers):
    return ET.fromstring(
    requests.get(
    url,
    headers=headers
    ).content
    )

    def fillup(config, host):
    result = {
    'ansible_host': search(
    host,
    '{http://www.vmware.com/vcloud/v1.5}IpAddress',
    True,
    ''
    )
    }

    result.update(config['globals'])

    if host.get('name') in config['override'].keys():
    result.update(config['override'][host.get('name')])

    return result

    def search(root, tag, text, attr):
    for elem in root.iter(tag):
    if text:
    return elem.text
    else:
    return elem.get(attr)

    def groups(root, vapp, host):
    name = vapp.get('name')

    if not name in root:
    root[name] = {
    'hosts': []
    }

    root[name]['hosts'].append(search(
    host,
    '{http://www.vmware.com/vcloud/v1.5}Vm',
    False,
    "name"
    ))

    if __name__ == '__main__':
    main()
    70 changes: 70 additions & 0 deletions output.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,70 @@
    {
    "server": {
    "hosts": [
    "bastion-01",
    "bastion-02",
    "haproxy-01",
    "haproxy-02",
    "mariadb-01",
    "mariadb-02"
    ]
    },
    "_meta": {
    "hostvars": {
    "bastion-01": {
    "ansible_host": "192.168.1.1",
    "ansible_become": "true",
    "ansible_python_interpreter": "/usr/bin/python3",
    "ansible_ssh_common_args": "-o StrictHostKeyChecking=no"
    },
    "bastion-02": {
    "ansible_host": "192.168.1.2",
    "ansible_become": "true",
    "ansible_python_interpreter": "/usr/bin/python3",
    "ansible_ssh_common_args": "-o StrictHostKeyChecking=no"
    },
    "haproxy-01": {
    "ansible_host": "10.10.0.21",
    "ansible_become": "true",
    "ansible_python_interpreter": "/usr/bin/python3",
    "ansible_ssh_common_args": "-o StrictHostKeyChecking=no -o ProxyCommand=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -W %h:%p [email protected]\""
    },
    "haproxy-02": {
    "ansible_host": "10.10.0.22",
    "ansible_become": "true",
    "ansible_python_interpreter": "/usr/bin/python3",
    "ansible_ssh_common_args": "-o StrictHostKeyChecking=no -o ProxyCommand=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -W %h:%p [email protected]\""
    },
    "mariadb-01": {
    "ansible_host": "10.10.8.1",
    "ansible_become": "true",
    "ansible_python_interpreter": "/usr/bin/python3",
    "ansible_ssh_common_args": "-o StrictHostKeyChecking=no -o ProxyCommand=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -W %h:%p [email protected]\""
    },
    "mariadb-02": {
    "ansible_host": "10.10.8.2",
    "ansible_become": "true",
    "ansible_python_interpreter": "/usr/bin/python3",
    "ansible_ssh_common_args": "-o StrictHostKeyChecking=no -o ProxyCommand=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -W %h:%p [email protected]\""
    }
    }
    },
    "bastion": {
    "hosts": [
    "bastion-01",
    "bastion-02"
    ]
    },
    "haproxy": {
    "hosts": [
    "haproxy-01",
    "haproxy-02"
    ]
    },
    "mariadb": {
    "hosts": [
    "mariadb-01",
    "mariadb-02"
    ]
    }
    }