Zack Williams | caf0566 | 2020-10-09 19:52:40 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | |
| 3 | # SPDX-FileCopyrightText: © 2020 Open Networking Foundation <support@opennetworking.org> |
| 4 | # SPDX-License-Identifier: Apache-2.0 |
| 5 | |
| 6 | from __future__ import absolute_import |
| 7 | |
| 8 | import argparse |
| 9 | import json |
| 10 | import logging |
| 11 | import netaddr |
| 12 | import re |
| 13 | import ssl |
| 14 | import urllib.parse |
| 15 | import urllib.request |
| 16 | from ruamel import yaml |
| 17 | |
| 18 | # create shared logger |
| 19 | logging.basicConfig() |
| 20 | logger = logging.getLogger("nbht") |
| 21 | |
| 22 | # headers to pass, set globally |
| 23 | headers = [] |
| 24 | |
| 25 | # settings |
| 26 | settings = {} |
| 27 | |
| 28 | def parse_nb_args(): |
| 29 | """ |
| 30 | parse CLI arguments |
| 31 | """ |
| 32 | |
| 33 | parser = argparse.ArgumentParser(description="NetBox Host Descriptions") |
| 34 | |
| 35 | # Positional args |
| 36 | parser.add_argument( |
| 37 | "settings", |
| 38 | type=argparse.FileType("r"), |
| 39 | help="YAML ansible inventory file w/netbox info", |
| 40 | ) |
| 41 | |
| 42 | parser.add_argument( |
| 43 | "--debug", action="store_true", help="Print additional debugging information" |
| 44 | ) |
| 45 | |
| 46 | return parser.parse_args() |
| 47 | |
| 48 | |
| 49 | def json_api_get( |
| 50 | url, |
| 51 | headers, |
| 52 | data=None, |
| 53 | trim_prefix=False, |
| 54 | allow_failure=False, |
| 55 | validate_certs=False, |
| 56 | ): |
| 57 | """ |
| 58 | Call JSON API endpoint, return data as a dict |
| 59 | """ |
| 60 | |
| 61 | logger.debug("json_api_get url: %s", url) |
| 62 | |
| 63 | # if data included, encode it as JSON |
| 64 | if data: |
| 65 | data_enc = str(json.dumps(data)).encode("utf-8") |
| 66 | |
| 67 | request = urllib.request.Request(url, data=data_enc, method="POST") |
| 68 | request.add_header("Content-Type", "application/json; charset=UTF-8") |
| 69 | else: |
| 70 | request = urllib.request.Request(url) |
| 71 | |
| 72 | # add headers tuples |
| 73 | for header in headers: |
| 74 | request.add_header(*header) |
| 75 | |
| 76 | try: |
| 77 | |
| 78 | if validate_certs: |
| 79 | response = urllib.request.urlopen(request) |
| 80 | |
| 81 | else: |
| 82 | ctx = ssl.create_default_context() |
| 83 | ctx.check_hostname = False |
| 84 | ctx.verify_mode = ssl.CERT_NONE |
| 85 | |
| 86 | response = urllib.request.urlopen(request, context=ctx) |
| 87 | |
| 88 | except urllib.error.HTTPError: |
| 89 | # asking for data that doesn't exist results in a 404, just return nothing |
| 90 | if allow_failure: |
| 91 | return None |
| 92 | logger.exception("Server encountered an HTTPError at URL: '%s'", url) |
| 93 | except urllib.error.URLError: |
| 94 | logger.exception("An URLError occurred at URL: '%s'", url) |
| 95 | else: |
| 96 | # docs: https://docs.python.org/3/library/json.html |
| 97 | jsondata = response.read() |
| 98 | logger.debug("API response: %s", jsondata) |
| 99 | |
| 100 | try: |
| 101 | data = json.loads(jsondata) |
| 102 | except json.decoder.JSONDecodeError: |
| 103 | # allow return of no data |
| 104 | if allow_failure: |
| 105 | return None |
| 106 | logger.exception("Unable to decode JSON") |
| 107 | else: |
| 108 | logger.debug("JSON decoded: %s", data) |
| 109 | |
| 110 | return data |
| 111 | |
| 112 | |
| 113 | def get_pxe_devices(tenant_group, filters=""): |
| 114 | |
| 115 | # get all devices in a prefix |
| 116 | url = "%s%s" % ( |
| 117 | settings["api_endpoint"], |
| 118 | "api/dcim/devices/?tenant_group=%s%s" % (tenant_group, filters), |
| 119 | ) |
| 120 | |
| 121 | print(url) |
| 122 | |
| 123 | raw_devs = json_api_get(url, headers, validate_certs=settings["validate_certs"]) |
| 124 | |
| 125 | logger.debug("raw_devs: %s", raw_devs) |
| 126 | |
| 127 | devs = [] |
| 128 | |
| 129 | for item in raw_devs["results"]: |
| 130 | dev = {} |
| 131 | dev["serial"] = item["serial"] |
| 132 | dev["hostname"] = item["name"] |
| 133 | dev["domain"] = "aetherproject.net" |
| 134 | |
| 135 | devs.append(dev) |
| 136 | |
| 137 | return devs |
| 138 | |
| 139 | |
| 140 | # main function that calls other functions |
| 141 | if __name__ == "__main__": |
| 142 | |
| 143 | args = parse_nb_args() |
| 144 | |
| 145 | # only print log messages if debugging |
| 146 | if args.debug: |
| 147 | logger.setLevel(logging.DEBUG) |
| 148 | else: |
| 149 | logger.setLevel(logging.INFO) |
| 150 | |
| 151 | # load settings from yaml file |
| 152 | settings = yaml.safe_load(args.settings.read()) |
| 153 | |
| 154 | logger.info("settings: %s" % settings) |
| 155 | |
| 156 | # global, so this isn't run multiple times |
| 157 | headers = [ |
| 158 | ("Authorization", "Token %s" % settings["token"]), |
| 159 | ] |
| 160 | |
| 161 | # create structure from extracted data |
| 162 | |
| 163 | pxe_devices = get_pxe_devices("aether", "&role_id=1") |
| 164 | |
| 165 | print(yaml.safe_dump({"pxeboot_hosts": pxe_devices}, indent=2)) |