Created
January 22, 2026 16:49
-
-
Save webtweakers/4e6af09ebb64fd0352581428d22e0e56 to your computer and use it in GitHub Desktop.
Opalstack API Proxy Script for Ansible
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Universal Opalstack API Proxy Script for Ansible. | |
| Usage: manage_opalstack.py <action> <resource> [key=value ...] | |
| """ | |
| import os | |
| import sys | |
| import json | |
| import argparse | |
| from opalstack.api import Api | |
| def get_api(): | |
| api_token = os.environ.get('OPALSTACK_TOKEN') | |
| if not api_token: | |
| raise ValueError("Environment variable OPALSTACK_TOKEN must be set.") | |
| return Api(token=api_token) | |
| def exit_json(msg, changed=False, data=None): | |
| response = {"failed": False, "msg": msg, "changed": changed, "data": data} | |
| # Extract ID for easier Ansible access | |
| # Opalstack 'create' usually returns a list of objects | |
| if isinstance(data, list) and len(data) > 0 and isinstance(data[0], dict): | |
| if 'id' in data[0]: | |
| response['id'] = data[0]['id'] | |
| elif isinstance(data, dict) and 'id' in data: | |
| response['id'] = data['id'] | |
| print(json.dumps(response)) | |
| sys.exit(0) | |
| def fail_json(msg, error=None): | |
| print(json.dumps({"failed": True, "msg": msg, "error": str(error), "id": None})) | |
| sys.exit(1) | |
| def find_existing(manager, resource_type, search_params): | |
| """Internal helper to find an existing resource matching key parameters.""" | |
| raw_data = manager.list_all() | |
| items = [] | |
| if isinstance(raw_data, dict): | |
| for cat in raw_data.values(): | |
| if isinstance(cat, list): items.extend(cat) | |
| else: | |
| items = raw_data | |
| for item in items: | |
| is_match = True | |
| for k, v in search_params.items(): | |
| target_key = 'hostname' if resource_type == 'servers' and k == 'name' else k | |
| val = item.get(target_key) | |
| # Handle nested objects like {'id': '...', 'hostname': '...'} | |
| if isinstance(val, dict) and 'id' in val: | |
| val = val['id'] | |
| if str(val) != str(v): | |
| is_match = False | |
| break | |
| if is_match: | |
| return item | |
| return None | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Universal Opalstack API Proxy") | |
| parser.add_argument('action', help="API action: create, read, update, delete, list, or 'get'") | |
| parser.add_argument('resource', help="API resource: psqldbs, dbusers, apps, etc.") | |
| parser.add_argument('params', nargs='*', help="Dynamic key=value pairs for the API") | |
| args = parser.parse_args() | |
| try: | |
| api = get_api() | |
| if not hasattr(api, args.resource): | |
| fail_json(f"Resource '{args.resource}' not found.") | |
| manager = getattr(api, args.resource) | |
| api_kwargs = {} | |
| for pair in args.params: | |
| if '=' in pair: | |
| key, value = pair.split('=', 1) | |
| stripped_val = value.strip() | |
| if (stripped_val.startswith('[') and stripped_val.endswith(']')) or \ | |
| (stripped_val.startswith('{') and stripped_val.endswith('}')): | |
| try: | |
| value = json.loads(value) | |
| except json.JSONDecodeError: | |
| pass | |
| elif value.lower() == 'true': | |
| value = True | |
| elif value.lower() == 'false': | |
| value = False | |
| api_kwargs[key] = value | |
| # ACTION: GET | |
| if args.action == 'get': | |
| match = find_existing(manager, args.resource, api_kwargs) | |
| if match: | |
| exit_json(f"Found {args.resource}", data=match) | |
| else: | |
| exit_json(f"Resource {args.resource} with args {api_kwargs} not found", data={'id': None}) | |
| # ACTION: CREATE (IDEMPOTENT) | |
| if args.action == 'create': | |
| search_keys = ['name', 'hostname', 'server'] | |
| search_params = {k: v for k, v in api_kwargs.items() if k in search_keys} | |
| if search_params: | |
| existing = find_existing(manager, args.resource, search_params) | |
| if existing: | |
| exit_json(f"{args.resource} already exists", changed=False, data=existing) | |
| # STANDARD API ACTIONS | |
| if not hasattr(manager, args.action): | |
| fail_json(f"Action '{args.action}' not supported for resource '{args.resource}'.") | |
| method = getattr(manager, args.action) | |
| if args.action in ['create', 'update']: | |
| result = method([api_kwargs]) | |
| elif args.action in ['delete', 'read'] and 'id' in api_kwargs: | |
| result = method(api_kwargs.pop('id')) | |
| else: | |
| result = method(api_kwargs) | |
| changed = True if args.action in ['create', 'delete', 'update'] else False | |
| exit_json(f"Success: {args.action} {args.resource}", changed=changed, data=result) | |
| except Exception as e: | |
| fail_json(f"API Error during {args.action} {args.resource}", error=e) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment