2026-01-14 03:09:18 +00:00
|
|
|
# ADR-017: Plugin Wrapper Abstraction Framework\n\n**Status**: Proposed\n**Date**: 2026-01-13\n**Author**: Architecture Team\n**Supersedes**: Manual plugin wrapper implementations in `lib_provisioning/plugins/`\n\n## Context\n\nThe provisioning system integrates with four critical plugins, each with its own wrapper layer:\n\n1. **auth.nu** (1066 lines) - Authentication plugin wrapper\n2. **orchestrator.nu** (~500 lines) - Orchestrator plugin wrapper\n3. **secretumvault.nu** (~500 lines) - Secrets vault plugin wrapper\n4. **kms.nu** (~500 lines) - Key management service plugin wrapper\n\nAnalysis reveals ~90% code duplication across these wrappers:\n\n```\n# Pattern repeated 4 times with minor variations:\nexport def plugin-available? [] {\n # Check if plugin is installed\n}\n\nexport def try-plugin-call [method args] {\n # Try to call the plugin\n # On failure, fallback to HTTP\n}\n\nexport def http-fallback-call [endpoint method args] {\n # HTTP endpoint fallback\n}\n```\n\n## Problem Statement\n\n**Current Architecture**:\n- Each plugin has manual wrapper implementation\n- ~3000 total lines across 4 files\n- Boilerplate code repeated for each plugin method\n- HTTP fallback logic duplicated\n- Error handling inconsistent\n- Testing each wrapper requires custom setup\n\n**Key Metrics**:\n- 3000 lines of plugin wrapper code\n- 90% code similarity\n- 85% reduction opportunity\n\n## Decision\n\nImplement **Plugin Wrapper Abstraction Framework**: replace manual plugin wrappers with a generic proxy framework + declarative YAML definitions.\n\n### Architecture\n\n```\nPlugin Definition (YAML)\n ├─ plugin: auth\n ├─ methods:\n │ ├─ login(username, password)\n │ ├─ logout()\n │ └─ status()\n └─ http_endpoint: http://localhost:8001\n\nGeneric Plugin Proxy Framework\n ├─ availability() - Check if plugin installed\n ├─ call() - Try plugin, fallback to HTTP\n ├─ http_fallback() - HTTP call with retry\n └─ error_handler() - Consistent error handling\n\nGenerated Wrappers\n ├─ auth_wrapper.nu (150 lines, autogenerated)\n ├─ orchestrator_wrapper.nu (150 lines)\n ├─ vault_wrapper.nu (150 lines)\n └─ kms_wrapper.nu (150 lines)\n```\n\n### Mechanism\n\n**Plugin Call Flow**:\n\n1. **Check Availability**: Is plugin installed and running?\n2. **Try Plugin Call**: Execute plugin method with timeout\n3. **On Failure**: Fall back to HTTP endpoint\n4. **Error Handling**: Unified error response format\n5. **Retry Logic**: Configurable retry with exponential backoff\n\n### Error Handling Pattern\n\n**Nushell 0.109 Compliant** (do-complete pattern, no try-catch):\n\n```\ndef call-plugin-with-fallback [method: string args: record] {\n let plugin_result = (\n do {\n # Try plugin call\n call-plugin $method $args\n } | complete\n )\n\n if $plugin_result.exit_code != 0 {\n # Fall back to HTTP\n call-http-endpoint $method $args\n } else {\n $plugin_result.stdout | from json\n }\n}\n```\n\n## Consequences\n\n### Positive\n\n- **85% Code Reduction**: 3000 lines → 200 (proxy) + 600 (generated)\n- **Consistency**: All plugins use identical call pattern\n- **Maintainability**: Single proxy implementation vs 4 wrapper files\n- **Testability**: Mock proxy for testing, no plugin-specific setup needed\n- **Extensibility**: New plugins require only YAML definition\n\n### Negative\n\n- **Abstraction Overhead**: Proxy layer adds indirection\n- **YAML Schema**: Must maintain schema for plugin definitions\n- **Migration Risk**: Replacing working code requires careful testing\n\n## Implementation Strategy\n\n1. **Create Generic Proxy** (`lib_provisioning/plugins/proxy.nu`)\n - Plugin availability detection\n - Call execution with error handling\n - HTTP fallback mechanism\n - Retry logic with backoff\n\n2. **Define Plugin Schema** (`lib_provisioning/plugins/definitions/plugin.schema.yaml`)\n - Plugin metadata (name, http_endpoint)\n - Method definitions (parameters, return types)\n - Fallback
|