2025-10-07 10:59:52 +01:00

527 lines
14 KiB
Plaintext

#!/usr/bin/env nu
# Integration Functions for External Systems
#
# Provides integration with:
# - MCP (Model Context Protocol) servers
# - Rust installer binary
# - REST APIs
# - Webhook notifications
# Load configuration from MCP server
#
# Queries the MCP server for deployment configuration using
# the Model Context Protocol.
#
# @param mcp_url: MCP server URL
# @returns: Deployment configuration record
export def load-config-from-mcp [mcp_url: string]: nothing -> record {
print $"📡 Loading configuration from MCP server: ($mcp_url)"
# MCP request payload
let request = {
jsonrpc: "2.0"
id: 1
method: "config/get"
params: {
type: "deployment"
include_defaults: true
}
}
try {
let response = (
http post $mcp_url --content-type "application/json" ($request | to json)
)
if "error" in ($response | columns) {
error make {
msg: $"MCP error: ($response.error.message)"
label: {text: $"Code: ($response.error.code)"}
}
}
if "result" not-in ($response | columns) {
error make {msg: "Invalid MCP response: missing result"}
}
print "✅ Configuration loaded from MCP server"
$response.result
} catch {|err|
error make {
msg: $"Failed to load config from MCP: ($mcp_url)"
label: {text: $err.msg}
help: "Ensure MCP server is running and accessible"
}
}
}
# Load configuration from REST API
#
# Fetches deployment configuration from a REST API endpoint.
#
# @param api_url: API endpoint URL
# @returns: Deployment configuration record
export def load-config-from-api [api_url: string]: nothing -> record {
print $"🌐 Loading configuration from API: ($api_url)"
try {
let response = (http get $api_url --max-time 30sec)
if "config" not-in ($response | columns) {
error make {msg: "Invalid API response: missing 'config' field"}
}
print "✅ Configuration loaded from API"
$response.config
} catch {|err|
error make {
msg: $"Failed to load config from API: ($api_url)"
label: {text: $err.msg}
help: "Check API endpoint and network connectivity"
}
}
}
# Send notification to webhook
#
# Sends deployment event notifications to a configured webhook URL.
# Useful for integration with Slack, Discord, Microsoft Teams, etc.
#
# @param webhook_url: Webhook URL
# @param payload: Notification payload record
# @returns: Nothing
export def notify-webhook [webhook_url: string, payload: record]: nothing -> nothing {
try {
http post $webhook_url --content-type "application/json" ($payload | to json)
null
} catch {|err|
# Don't fail deployment on webhook errors, just log
print $"⚠️ Warning: Failed to send webhook notification: ($err.msg)"
null
}
}
# Call Rust installer binary with arguments
#
# Invokes the Rust installer binary with specified arguments,
# capturing output and exit code.
#
# @param args: List of arguments to pass to installer
# @returns: Installer execution result record
export def call-installer [args: list<string>]: nothing -> record {
let installer_path = get-installer-path
print $"🚀 Calling installer: ($installer_path) ($args | str join ' ')"
try {
let output = (^$installer_path ...$args | complete)
{
success: ($output.exit_code == 0)
exit_code: $output.exit_code
stdout: $output.stdout
stderr: $output.stderr
timestamp: (date now)
}
} catch {|err|
{
success: false
exit_code: -1
error: $err.msg
timestamp: (date now)
}
}
}
# Run installer in headless mode with config file
#
# Executes the Rust installer in headless mode using a
# configuration file.
#
# @param config_path: Path to configuration file
# @param auto_confirm: Auto-confirm prompts
# @returns: Installer execution result record
export def run-installer-headless [
config_path: path
--auto-confirm
]: nothing -> record {
mut args = ["--headless", "--config", $config_path]
if $auto_confirm {
$args = ($args | append "--yes")
}
call-installer $args
}
# Run installer interactively
#
# Launches the Rust installer in interactive TUI mode.
#
# @returns: Installer execution result record
export def run-installer-interactive []: nothing -> record {
let installer_path = get-installer-path
print $"🚀 Launching interactive installer: ($installer_path)"
try {
# Run without capturing output (interactive mode)
^$installer_path
{
success: true
mode: "interactive"
message: "Interactive installer completed"
timestamp: (date now)
}
} catch {|err|
{
success: false
mode: "interactive"
error: $err.msg
timestamp: (date now)
}
}
}
# Pass deployment config to installer via CLI args
#
# Converts a deployment configuration record into CLI arguments
# for the Rust installer binary.
#
# @param config: Deployment configuration record
# @returns: List of CLI arguments
export def config-to-cli-args [config: record]: nothing -> list<string> {
mut args = ["--headless"]
# Add platform
$args = ($args | append ["--platform", $config.platform])
# Add mode
$args = ($args | append ["--mode", $config.mode])
# Add domain
$args = ($args | append ["--domain", $config.domain])
# Add services (comma-separated)
let services = $config.services
| where enabled
| get name
| str join ","
if $services != "" {
$args = ($args | append ["--services", $services])
}
$args
}
# Deploy using installer binary
#
# High-level function to deploy using the Rust installer binary
# with the given configuration.
#
# @param config: Deployment configuration record
# @param auto_confirm: Auto-confirm prompts
# @returns: Deployment result record
export def deploy-with-installer [
config: record
--auto-confirm
]: nothing -> record {
print "🚀 Deploying using Rust installer binary..."
# Convert config to CLI args
mut args = (config-to-cli-args $config)
if $auto_confirm {
$args = ($args | append "--yes")
}
# Execute installer
let result = call-installer $args
if $result.success {
print "✅ Installer deployment successful"
{
success: true
method: "installer_binary"
config: $config
timestamp: (date now)
}
} else {
print $"❌ Installer deployment failed: ($result.stderr)"
{
success: false
method: "installer_binary"
error: $result.stderr
exit_code: $result.exit_code
timestamp: (date now)
}
}
}
# Query MCP server for deployment status
#
# Retrieves deployment status information from MCP server.
#
# @param mcp_url: MCP server URL
# @param deployment_id: Deployment identifier
# @returns: Deployment status record
export def query-mcp-status [mcp_url: string, deployment_id: string]: nothing -> record {
let request = {
jsonrpc: "2.0"
id: 1
method: "deployment/status"
params: {
deployment_id: $deployment_id
}
}
try {
let response = (
http post $mcp_url --content-type "application/json" ($request | to json)
)
if "error" in ($response | columns) {
error make {
msg: $"MCP error: ($response.error.message)"
}
}
$response.result
} catch {|err|
error make {
msg: $"Failed to query MCP status: ($err.msg)"
}
}
}
# Register deployment with API
#
# Registers a new deployment with the external API and returns
# a deployment ID for tracking.
#
# @param api_url: API endpoint URL
# @param config: Deployment configuration
# @returns: Registration result with deployment ID
export def register-deployment-with-api [api_url: string, config: record]: nothing -> record {
let payload = {
platform: $config.platform
mode: $config.mode
domain: $config.domain
services: ($config.services | get name)
started_at: (date now | format date "%Y-%m-%dT%H:%M:%SZ")
}
try {
let response = (
http post $api_url --content-type "application/json" ($payload | to json)
)
if "deployment_id" not-in ($response | columns) {
error make {msg: "API did not return deployment_id"}
}
print $"✅ Deployment registered with API: ($response.deployment_id)"
{
success: true
deployment_id: $response.deployment_id
api_url: $api_url
}
} catch {|err|
print $"⚠️ Warning: Failed to register with API: ($err.msg)"
{
success: false
error: $err.msg
}
}
}
# Update deployment status via API
#
# Updates deployment status on external API for tracking and monitoring.
#
# @param api_url: API endpoint URL
# @param deployment_id: Deployment identifier
# @param status: Status update record
# @returns: Update result record
export def update-deployment-status [
api_url: string
deployment_id: string
status: record
]: nothing -> record {
let update_url = $"($api_url)/($deployment_id)/status"
try {
http patch $update_url --content-type "application/json" ($status | to json)
{success: true}
} catch {|err|
print $"⚠️ Warning: Failed to update deployment status: ($err.msg)"
{success: false, error: $err.msg}
}
}
# Send Slack notification
#
# Sends formatted notification to Slack webhook.
#
# @param webhook_url: Slack webhook URL
# @param message: Message text
# @param color: Message color (good, warning, danger)
# @returns: Nothing
export def notify-slack [
webhook_url: string
message: string
--color: string = "good"
]: nothing -> nothing {
let payload = {
attachments: [{
color: $color
text: $message
footer: "Provisioning Platform Installer"
ts: (date now | format date "%s")
}]
}
notify-webhook $webhook_url $payload
}
# Send Discord notification
#
# Sends formatted notification to Discord webhook.
#
# @param webhook_url: Discord webhook URL
# @param message: Message text
# @param success: Whether deployment was successful
# @returns: Nothing
export def notify-discord [
webhook_url: string
message: string
--success
]: nothing -> nothing {
let color = if $success { 3066993 } else { 15158332 } # Green or Red
let emoji = if $success { "✅" } else { "❌" }
let payload = {
embeds: [{
title: $"($emoji) Provisioning Platform Deployment"
description: $message
color: $color
timestamp: (date now | format date "%Y-%m-%dT%H:%M:%SZ")
footer: {
text: "Provisioning Platform Installer"
}
}]
}
notify-webhook $webhook_url $payload
}
# Send Microsoft Teams notification
#
# Sends formatted notification to Microsoft Teams webhook.
#
# @param webhook_url: Teams webhook URL
# @param title: Notification title
# @param message: Message text
# @param success: Whether deployment was successful
# @returns: Nothing
export def notify-teams [
webhook_url: string
title: string
message: string
--success
]: nothing -> nothing {
let theme_color = if $success { "00FF00" } else { "FF0000" }
let payload = {
"@type": "MessageCard"
"@context": "https://schema.org/extensions"
summary: $title
themeColor: $theme_color
title: $title
text: $message
}
notify-webhook $webhook_url $payload
}
# Execute MCP tool call
#
# Executes a tool/function call via MCP server.
#
# @param mcp_url: MCP server URL
# @param tool_name: Name of tool to execute
# @param arguments: Tool arguments record
# @returns: Tool execution result
export def execute-mcp-tool [
mcp_url: string
tool_name: string
arguments: record
]: nothing -> record {
let request = {
jsonrpc: "2.0"
id: 1
method: "tools/call"
params: {
name: $tool_name
arguments: $arguments
}
}
try {
let response = (
http post $mcp_url --content-type "application/json" ($request | to json)
)
if "error" in ($response | columns) {
error make {
msg: $"MCP tool execution error: ($response.error.message)"
}
}
$response.result
} catch {|err|
error make {
msg: $"Failed to execute MCP tool: ($err.msg)"
}
}
}
# Get installer binary path (helper function)
#
# @returns: Path to installer binary
def get-installer-path []: nothing -> path {
let installer_dir = $env.PWD | path dirname
let installer_name = if $nu.os-info.name == "windows" {
"provisioning-installer.exe"
} else {
"provisioning-installer"
}
# Check target/release first, then target/debug
let release_path = $installer_dir | path join "target" "release" $installer_name
let debug_path = $installer_dir | path join "target" "debug" $installer_name
if ($release_path | path exists) {
$release_path
} else if ($debug_path | path exists) {
$debug_path
} else {
error make {
msg: "Installer binary not found"
help: "Build with: cargo build --release"
}
}
}