library/calendar_sync.py hinzugefügt
This commit is contained in:
159
library/calendar_sync.py
Normal file
159
library/calendar_sync.py
Normal file
@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from caldav import DAVClient, Calendar
|
||||
import vobject
|
||||
import requests
|
||||
import logging
|
||||
|
||||
# Logging für Debugging einrichten
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
|
||||
def fetch_ics_events(url, user=None, password=None, verify_ssl=True):
|
||||
try:
|
||||
response = requests.get(url, auth=(user, password) if user else None, verify=verify_ssl)
|
||||
response.raise_for_status()
|
||||
events = []
|
||||
ics_data = response.text
|
||||
|
||||
if not ics_data.strip():
|
||||
logging.warning("Leere ICS-Daten empfangen")
|
||||
return events
|
||||
|
||||
try:
|
||||
for calendar in vobject.readComponents(ics_data):
|
||||
for component in calendar.components():
|
||||
if component.name == 'VEVENT':
|
||||
if hasattr(component, 'uid') and component.uid.value:
|
||||
events.append(component)
|
||||
else:
|
||||
logging.warning("VEVENT ohne UID übersprungen")
|
||||
except vobject.base.ParseError as e:
|
||||
logging.error(f"Parse-Fehler in ICS-Daten: {str(e)}")
|
||||
return events
|
||||
except Exception as e:
|
||||
raise Exception(f"ICS-Fehler: {str(e)}")
|
||||
|
||||
def connect_caldav(url, user=None, password=None, verify_ssl=True):
|
||||
try:
|
||||
client = DAVClient(
|
||||
url,
|
||||
username=user,
|
||||
password=password,
|
||||
ssl_verify_cert=verify_ssl
|
||||
)
|
||||
# Direktes Calendar-Objekt, kein principal-Abgleich nötig
|
||||
return Calendar(client=client, url=url)
|
||||
except Exception as e:
|
||||
raise Exception(f"CalDAV-Fehler: {str(e)}")
|
||||
|
||||
def safe_caldav_event_processing(event):
|
||||
try:
|
||||
if event is None or not hasattr(event, 'data') or not event.data:
|
||||
return None
|
||||
for calendar in vobject.readComponents(event.data):
|
||||
for component in calendar.components():
|
||||
if component.name == 'VEVENT' and hasattr(component, 'uid'):
|
||||
return {
|
||||
'uid': str(component.uid),
|
||||
'event_object': event,
|
||||
'data': event.data
|
||||
}
|
||||
return None
|
||||
except vobject.base.ParseError:
|
||||
logging.warning("Parse-Fehler in CalDAV-Event, überspringe")
|
||||
return None
|
||||
except Exception as e:
|
||||
logging.error(f"Fehler bei Event-Verarbeitung: {str(e)}")
|
||||
return None
|
||||
|
||||
def sync_ics_to_caldav(module):
|
||||
ics_events = fetch_ics_events(
|
||||
module.params['source_url'],
|
||||
module.params['source_user'],
|
||||
module.params['source_password'],
|
||||
module.params['verify_ssl']
|
||||
)
|
||||
|
||||
target_cal = connect_caldav(
|
||||
module.params['target_url'],
|
||||
module.params['target_user'],
|
||||
module.params['target_password'],
|
||||
module.params['verify_ssl']
|
||||
)
|
||||
|
||||
existing_events = {}
|
||||
for event in target_cal.events():
|
||||
processed = safe_caldav_event_processing(event)
|
||||
if processed:
|
||||
existing_events[processed['uid']] = processed
|
||||
|
||||
changed = False
|
||||
results = {'added': [], 'updated': [], 'removed': []}
|
||||
|
||||
for vevent in ics_events:
|
||||
uid = str(vevent.uid)
|
||||
try:
|
||||
ical_data = vevent.serialize()
|
||||
except Exception as e:
|
||||
logging.error(f"Serialisierungsfehler für UID {uid}: {str(e)}")
|
||||
continue
|
||||
|
||||
if not ical_data.strip():
|
||||
logging.warning(f"Leere iCal-Daten für UID {uid}, überspringe")
|
||||
continue
|
||||
|
||||
if uid not in existing_events:
|
||||
try:
|
||||
target_cal.add_event(ical_data)
|
||||
results['added'].append(uid)
|
||||
changed = True
|
||||
except Exception as e:
|
||||
logging.error(f"Hinzufügen fehlgeschlagen für UID {uid}: {str(e)}")
|
||||
else:
|
||||
existing_data = existing_events[uid]['data']
|
||||
if ical_data != existing_data:
|
||||
try:
|
||||
event_obj = existing_events[uid]['event_object']
|
||||
event_obj.data = ical_data
|
||||
event_obj.save()
|
||||
results['updated'].append(uid)
|
||||
changed = True
|
||||
except Exception as e:
|
||||
logging.error(f"Aktualisieren fehlgeschlagen für UID {uid}: {str(e)}")
|
||||
|
||||
if module.params['purge']:
|
||||
current_uids = {str(e.uid) for e in ics_events}
|
||||
for uid, event_info in existing_events.items():
|
||||
if uid not in current_uids:
|
||||
try:
|
||||
event_info['event_object'].delete()
|
||||
results['removed'].append(uid)
|
||||
changed = True
|
||||
except Exception as e:
|
||||
logging.error(f"Löschen fehlgeschlagen für UID {uid}: {str(e)}")
|
||||
|
||||
return changed, results
|
||||
|
||||
def run_module():
|
||||
module_args = dict(
|
||||
source_url=dict(type='str', required=True),
|
||||
source_user=dict(type='str', required=False, default=None),
|
||||
source_password=dict(type='str', required=False, no_log=True, default=None),
|
||||
target_url=dict(type='str', required=True),
|
||||
target_user=dict(type='str', required=False, default=None),
|
||||
target_password=dict(type='str', required=False, no_log=True, default=None),
|
||||
verify_ssl=dict(type='bool', default=False),
|
||||
purge=dict(type='bool', default=False)
|
||||
)
|
||||
|
||||
module = AnsibleModule(argument_spec=module_args, supports_check_mode=False)
|
||||
|
||||
try:
|
||||
changed, results = sync_ics_to_caldav(module)
|
||||
module.exit_json(changed=changed, **results)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_module()
|
||||
Reference in New Issue
Block a user