Commit 34117536 authored by Morten Brekkevold's avatar Morten Brekkevold
Browse files

Merge branch 'sync-rooms' into 'master'

New script to sync KIND-nettinst -> NAV-room

See merge request !2
parents cd541dee 8f4a9cb1
Synchronization of equipment from Kind to NAV
=============================================
usage: kindnavsync [-h] [--dry-run] [--kind-only] [--debug]
[--api-url API_URL] [--api-token API_TOKEN]
[--no-timestamps]
Synchronizes devices in NAV with those from Kind
optional arguments:
-h, --help show this help message and exit
--dry-run, -n don't change anything in NAV, just print what would
have been done
--kind-only only check contents of Kind, do not talk to NAV
--debug, -d enable debug logging
--api-url API_URL, -a API_URL
URL to the NAV API. Default=http://localhost/api/1
--api-token API_TOKEN, -t API_TOKEN
NAV API Token
--no-timestamps Don't add timestamps to log output
Since Kind only stores names of devices, while NAV requires specific IP
addresses, up-to-date DNS entries are required. If the environment variable
SLACK_URL is set, this program will post log messages to Slack using this as a
webhook URL.
Supported environment variables
-------------------------------
- `NAV_API_URL`: URL to NAV API
- `NAV_API_TOKEN`: NAV API token, providing at least read/write access for the
`/netbox` and `/room` endpoints.
- `SLACK_URL`: If set, log output will be posted to this slack webhook URL. See
<https://api.slack.com/incoming-webhooks> for details.
Notes
-----
- This program will not touch NAV devices owned by the organizational units
`urc` or `privat`.
- All devices will be set as owned by the organizational unit `uninett`.
- _"Nettinstallasjon"_ will be used as the device room in NAV.
- Any new room that this program creates in NAV will have its location set to
`norge`.
Bugs
----
- Doesn't update IP addresses in NAV if DNS changes.
* Synchronization of data from Kind to NAV
:PROPERTIES:
:CUSTOM_ID: synchronization-of-equipment-from-kind-to-nav
:END:
** Synchronization of equipment from Kind to NAV
#+BEGIN_EXAMPLE
usage: kindnavsync [-h] [--dry-run] [--kind-only] [--debug]
[--api-url API_URL] [--api-token API_TOKEN]
[--no-timestamps]
Synchronizes devices in NAV with those from Kind
optional arguments:
-h, --help show this help message and exit
--dry-run, -n don't change anything in NAV, just print what would
have been done
--kind-only only check contents of Kind, do not talk to NAV
--debug, -d enable debug logging
--api-url API_URL, -a API_URL
URL to the NAV API. Default=http://localhost/api/1
--api-token API_TOKEN, -t API_TOKEN
NAV API Token
--no-timestamps Don't add timestamps to log output
Since Kind only stores names of devices, while NAV requires specific IP
addresses, up-to-date DNS entries are required. If the environment variable
SLACK_URL is set, this program will post log messages to Slack using this as a
webhook URL.
#+END_EXAMPLE
** Supported environment variables
:PROPERTIES:
:CUSTOM_ID: supported-environment-variables
:END:
- =NAV_API_URL=: URL to NAV API
- =NAV_API_TOKEN=: NAV API token, providing at least read/write access
for the =/netbox= and =/room= endpoints.
- =SLACK_URL=: If set, log output will be posted to this slack webhook
URL. See [[https://api.slack.com/incoming-webhooks]] for details.
** Notes
:PROPERTIES:
:CUSTOM_ID: notes
:END:
- This program will not touch NAV devices owned by the organizational
units =urc= or =privat=.
- All devices will be set as owned by the organizational unit =uninett=.
- /"Nettinstallasjon"/ will be used as the device room in NAV.
- Any new room that this program creates in NAV will have its location
set to =norge=.
** Bugs
:PROPERTIES:
:CUSTOM_ID: bugs
:END:
- Doesn't update IP addresses in NAV if DNS changes.
** Synchronization of rooms from KIND to NAV
#+BEGIN_EXAMPLE
usage: nettinst2room [--debug]
[--api-url API_URL] [--api-token API_TOKEN]
[--room <roomname>]
#+END_EXAMPLE
Syncs room data from KIND to NAV. Takes an optional argument ~--room~
if only a specific room is to be synced. Default operation is to sync
all rooms returned by the API at https://kind.uninett.no/api/nettinstallasjoner.json
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Synkronisering av KIND/nettinstallasjon -> NAV/room
"""
import requests
import argparse
import logging
import sys
import os
from deepdiff import DeepDiff
import collections.abc
import copy
import urllib.parse
from kindnavsync.navapi import NAVAPI, ObjectNotFound
NAV_API_URL = "https://uninav.uninett.no/api/1"
NAV_API_TIMEOUT = 10.0
KIND_SERVICE_URL = "https://kind.uninett.no/api/nettinstallasjoner.json"
DEFAULT_ORGANIZATION = "uninett"
DEFAULT_LOCATION = "norge"
LOG = logging.getLogger("nettinst2room")
def main():
args = parse_args()
room = args.room
nettinst = get_kind_data(args.room)
api_url = args.api_url
api_token = args.api_token
nav_api = NAVAPI(url=api_url, auth_token=api_token, timeout=NAV_API_TIMEOUT)
LOG.setLevel(logging.DEBUG if args.debug else logging.INFO)
LOG.addHandler(logging.StreamHandler())
for ni in nettinst:
try:
room_response = nav_api.get_room(ni["navn"])
room = room_response.body
LOG.debug("Fant rom: %s", room)
except ObjectNotFound:
LOG.debug("Creating room for: %s", ni["navn"])
room = dict(id=ni["navn"], location=DEFAULT_LOCATION)
try:
nav_api.post_room(room)
except Exception as e:
LOG.error("Error in creating room for %s: ", ni["navn"])
LOG.error("Error is : %s", e)
continue
except Exception as e:
LOG.error("Error in fetching room for %s: ", ni["navn"])
LOG.error("Error: %s", e)
continue
# Creating a deep copy can make the patch bigger than needed...
patch = copy.deepcopy(room)
org = {}
# Update room attributes
org["location"] = DEFAULT_LOCATION
# FIXME, maybe add more into description?
desc_fields = ["beskrivelse", "termineringsadresse"]
description = ". ".join(
[ni[p].rstrip() for p in desc_fields if p in ni and ni[p]]
)
if not description:
description = "No description found"
org["description"] = description
if "org" in ni:
org["data"] = {
"site_owner": ni["org"],
"site_owner_url": "https://kind.uninett.no/"
+ str(int(ni["org_id"])),
}
if "lat" in ni and "lon" in ni:
org["position"] = [
"%.12f" % round(ni["lat"], 12),
"%.12f" % round(ni["lon"], 12),
]
# Merge data from KIND into existing entry
merge_dict(patch, org)
if DeepDiff(patch, room, ignore_order=True):
LOG.debug("Update needed")
LOG.debug(patch)
nav_api.patch_room(room_id=ni["navn"], patch=patch)
else:
LOG.debug("No update needed for room: %s", ni["navn"])
# https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth
def merge_dict(d, updates):
"""Merge entries from updates into d"""
for k, v in updates.items():
if isinstance(v, collections.abc.Mapping):
d[k] = merge_dict(d.get(k, {}), v)
else:
if k not in d:
LOG.debug("Setting %s to %s", k, v)
d[k] = v
elif d[k] != v:
LOG.debug("Updating %s from %s to %s", k, d[k], v)
d[k] = v
return d
def get_kind_data(room):
LOG.debug("Fetching data from: %s", KIND_SERVICE_URL)
return requests.get(KIND_SERVICE_URL, params={'navn': room}).json()
def parse_args():
parser = argparse.ArgumentParser(
description="Synchronizes rooms in NAV with those from Kind"
)
parser.add_argument(
"--room",
"-r",
help="Name of single room to sync. (All rooms otherwise)",
)
parser.add_argument(
"--api-url",
"-a",
default=os.environ.get("NAV_API_URL", NAV_API_URL),
help="URL to the NAV API. Default={}".format(NAV_API_URL),
)
parser.add_argument(
"--api-token",
"-t",
default=os.environ.get("NAV_API_TOKEN"),
help="NAV API Token",
)
parser.add_argument(
"--debug", "-d", action="store_true", help="enable debug logging"
)
args = parser.parse_args()
if not args.api_token:
parser.error(
"You must supply a NAV API token using either the "
"NAV_API_TOKEN environment variable, or the --api-token "
"option"
)
return args
if __name__ == "__main__":
main()
...@@ -80,6 +80,10 @@ class NAVAPI(object): ...@@ -80,6 +80,10 @@ class NAVAPI(object):
def post_room(self, room): def post_room(self, room):
return self.api.room.create(body=room) return self.api.room.create(body=room)
@_translate_exceptions
def patch_room(self, room_id, patch):
return self.api.room.partial_update(room_id, body=patch)
@functools.lru_cache(1) @functools.lru_cache(1)
@_translate_exceptions @_translate_exceptions
def get_version(self): def get_version(self):
......
...@@ -16,6 +16,7 @@ setup( ...@@ -16,6 +16,7 @@ setup(
'requests', 'requests',
'simple_rest_client', 'simple_rest_client',
'slack_log_handler', 'slack_log_handler',
'deepdiff',
], ],
python_requires='>3.7.0, <4', python_requires='>3.7.0, <4',
classifiers=[ classifiers=[
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment