# GitOps Implementation Fix for Portainer MCP Servers ## Problem The Portainer MCP servers were unable to enable GitOps on existing Git-based stacks. The `enable_stack_gitops` function in `portainer_gitops_server.py` was failing with "Invalid stack file content" errors. ## Root Cause The Portainer API's stack update endpoint (`PUT /api/stacks/{stack_id}`) has limitations: 1. It requires `StackFileContent` to be included in update requests 2. When `StackFileContent` is included, it converts Git-based stacks to file-based stacks 3. This sets `IsDetachedFromGit: true` and clears the `GitConfig` ## Solution Enable GitOps during stack creation rather than as a separate step. The API supports including `autoUpdate` configuration when creating stacks from Git repositories. ### Implementation Changes #### Updated `portainer_stacks_server.py` Added GitOps parameters to the `create_compose_stack_from_git` tool: ```python # New parameters in the tool schema: "enable_gitops": { "type": "boolean", "description": "Enable GitOps automatic updates", "default": False }, "gitops_interval": { "type": "string", "description": "GitOps polling interval (e.g., '5m', '1h')", "default": "5m" }, "gitops_mechanism": { "type": "string", "enum": ["polling", "webhook"], "description": "GitOps update mechanism", "default": "polling" }, "gitops_pull_image": { "type": "boolean", "description": "Pull latest images on GitOps update", "default": True }, "gitops_force_update": { "type": "boolean", "description": "Force redeployment even if no changes", "default": False } ``` The implementation now adds `autoUpdate` to the request when creating stacks: ```python if arguments.get("enable_gitops", False): auto_update = { "interval": arguments.get("gitops_interval", "5m"), "forcePullImage": arguments.get("gitops_pull_image", True), "forceUpdate": arguments.get("gitops_force_update", False) } if arguments.get("gitops_mechanism") == "webhook": auto_update["webhook"] = arguments.get("gitops_webhook_id", "") data["autoUpdate"] = auto_update ``` ## Usage ### Creating a Stack with GitOps Enabled ```python # Using the MCP server await tool.call("create_compose_stack_from_git", { "environment_id": "6", "name": "nginx-gitops", "repository_url": "https://github.com/example/repo", "repository_ref": "main", "compose_path": "docker-compose.yml", "enable_gitops": True, "gitops_interval": "5m", "gitops_mechanism": "polling", "gitops_pull_image": True }) ``` ### Direct API Call ```bash curl -X POST "https://portainer.example.com/api/stacks/create/standalone/repository?endpointId=6" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "name": "nginx-gitops", "repositoryURL": "https://github.com/example/repo", "repositoryReferenceName": "main", "composeFilePathInRepository": "docker-compose.yml", "repositoryAuthentication": false, "autoUpdate": { "interval": "5m", "forcePullImage": true, "forceUpdate": false } }' ``` ## Alternative Approaches If you need to enable GitOps on existing stacks: 1. **Delete and Recreate**: Delete the existing stack and recreate it with GitOps enabled 2. **Use Webhooks**: Configure webhooks at the Git repository level 3. **External Automation**: Use external tools to trigger stack updates via the Git redeploy endpoint ## Testing Use the provided `test_gitops_create.py` script to verify GitOps functionality: ```bash python test_gitops_create.py ``` This will: 1. Create a new stack with GitOps enabled 2. Verify the GitOps configuration 3. Display the stack details including AutoUpdate settings ## Limitations - Cannot enable GitOps on existing Git-based stacks without detaching them from Git - Cannot update GitOps settings (like polling interval) on existing stacks without detaching them from Git - The Portainer API does not provide a way to update Git-based stacks while maintaining Git connection - Any update that includes `StackFileContent` converts the stack from Git-based to file-based - This appears to be a fundamental limitation in Portainer's API design ### Why You Cannot Update GitOps Interval When attempting to update the GitOps interval (e.g., from 5m to 3m) on an existing stack: 1. **API Limitation**: The stack update endpoint (`PUT /api/stacks/{id}`) requires `StackFileContent` 2. **Side Effect**: Including `StackFileContent` in the update request: - Sets `IsDetachedFromGit: true` - Clears `GitConfig: null` - Clears `AutoUpdate: null` 3. **No Partial Updates**: The API doesn't support updating only the `AutoUpdate` configuration ### Workaround To change the GitOps interval, you must: 1. **Delete the existing stack** 2. **Recreate it with the new interval** Example: ```bash # Delete existing stack curl -X DELETE "https://portainer.example.com/api/stacks/{stack_id}?endpointId={env_id}" \ -H "X-API-Key: your-api-key" # Recreate with new interval curl -X POST "https://portainer.example.com/api/stacks/create/standalone/repository?endpointId={env_id}" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "name": "stack-name", "repositoryURL": "https://github.com/example/repo", "repositoryReferenceName": "refs/heads/main", "composeFilePathInRepository": "docker-compose.yml", "autoUpdate": { "interval": "3m", "forcePullImage": true, "forceUpdate": false } }' ``` ## Recommendations 1. Always enable GitOps during stack creation if needed 2. Consider opening a feature request with Portainer for enabling GitOps on existing stacks 3. Use the Git redeploy endpoint for manual updates of Git-based stacks ## API Field Reference The complete `autoUpdate` object structure: ```json { "autoUpdate": { "interval": "5m", // Polling interval (e.g., "1m30s", "5m", "1h") "forcePullImage": true, // Pull latest images on update "forceUpdate": false, // Force redeployment even if no changes "webhook": "webhook-id", // Webhook ID (optional, for webhook mechanism) "jobID": "15" // Job ID (managed by Portainer) } } ``` --- # Original GitOps MCP Server Documentation ## Features - **GitOps Configuration**: Enable/disable automatic updates for stacks - **Update Mechanisms**: Support for both webhook and polling-based updates - **Edge Stack Support**: GitOps for edge computing environments - **Webhook Management**: Generate and manage webhook URLs for CI/CD integration - **Git Credentials**: Manage authentication for private repositories - **Manual Triggers**: Trigger updates on-demand - **Update Windows**: Configure deployment windows (Business Edition) ## Installation 1. Ensure you have the Portainer MCP servers repository: ```bash git clone https://github.com/yourusername/portainer-mcp.git cd portainer-mcp ``` 2. Install dependencies: ```bash pip install mcp httpx aiohttp # Or use the requirements file: pip install -r requirements.txt ``` 3. Configure environment variables: ```bash cp .env.example .env # Edit .env with your Portainer URL and API key ``` 4. Make the server executable: ```bash chmod +x portainer_gitops_server.py ``` ## Configuration Add to your Claude Desktop configuration: ```json { "portainer-gitops": { "command": "python", "args": ["/path/to/portainer-mcp/portainer_gitops_server.py"], "env": { "PORTAINER_URL": "https://your-portainer-instance.com", "PORTAINER_API_KEY": "your-api-key" } } } ``` ## Available Tools ### Stack GitOps Management #### list_gitops_stacks List all stacks with GitOps configurations. - **Parameters**: - `environment_id` (optional): Filter by environment ID #### get_stack_gitops_config Get GitOps configuration for a specific stack. - **Parameters**: - `stack_id` (required): Stack ID #### enable_stack_gitops Enable GitOps automatic updates for a stack. - **Parameters**: - `stack_id` (required): Stack ID - `mechanism` (optional): Update mechanism - "webhook" or "polling" (default: "polling") - `interval` (optional): Polling interval like "5m", "1h" (default: "5m") - `force_update` (optional): Force redeployment even if no changes (default: false) - `pull_image` (optional): Pull latest images on update (default: true) #### disable_stack_gitops Disable GitOps automatic updates for a stack. - **Parameters**: - `stack_id` (required): Stack ID #### trigger_stack_update Manually trigger a GitOps update for a stack. - **Parameters**: - `stack_id` (required): Stack ID - `pull_image` (optional): Pull latest images (default: true) ### Webhook Management #### get_stack_webhook Get webhook URL for a stack. - **Parameters**: - `stack_id` (required): Stack ID #### regenerate_stack_webhook Regenerate webhook URL for a stack. - **Parameters**: - `stack_id` (required): Stack ID ### Edge Stack GitOps #### list_gitops_edge_stacks List all edge stacks with GitOps configurations. #### enable_edge_stack_gitops Enable GitOps for an edge stack. - **Parameters**: - `edge_stack_id` (required): Edge Stack ID - `mechanism` (optional): Update mechanism (default: "polling") - `interval` (optional): Polling interval (default: "5m") - `force_update` (optional): Force redeployment (default: false) #### get_edge_stack_webhook Get webhook URL for an edge stack. - **Parameters**: - `edge_stack_id` (required): Edge Stack ID ### Git Credential Management #### create_git_credential Create Git credentials for private repositories. - **Parameters**: - `name` (required): Credential name - `username` (required): Git username - `password` (required): Git password or personal access token #### list_git_credentials List all Git credentials. #### delete_git_credential Delete a Git credential. - **Parameters**: - `credential_id` (required): Credential ID ### GitOps Settings #### get_gitops_settings Get global GitOps settings. #### update_gitops_settings Update global GitOps settings. - **Parameters**: - `default_interval` (optional): Default polling interval - `concurrent_updates` (optional): Max concurrent updates - `update_window` (optional): Update window configuration - `enabled`: Enable update window - `start_time`: Window start time - `end_time`: Window end time - `timezone`: Timezone for window #### validate_git_repository Validate access to a Git repository. - **Parameters**: - `repository_url` (required): Git repository URL - `reference` (optional): Branch or tag (default: "main") - `credential_id` (optional): Credential ID for private repos ## Usage Examples ### Enable GitOps with Polling ```javascript // Enable polling-based GitOps await use_mcp_tool("portainer-gitops", "enable_stack_gitops", { stack_id: "5", mechanism: "polling", interval: "10m", force_update: false, pull_image: true }); // Check configuration await use_mcp_tool("portainer-gitops", "get_stack_gitops_config", { stack_id: "5" }); ``` ### Enable GitOps with Webhooks ```javascript // Enable webhook-based GitOps await use_mcp_tool("portainer-gitops", "enable_stack_gitops", { stack_id: "7", mechanism: "webhook", pull_image: true }); // Get webhook URL await use_mcp_tool("portainer-gitops", "get_stack_webhook", { stack_id: "7" }); ``` ### Manage Git Credentials ```javascript // Create credential for private repository await use_mcp_tool("portainer-gitops", "create_git_credential", { name: "GitHub PAT", username: "myusername", password: "ghp_xxxxxxxxxxxx" }); // List all credentials await use_mcp_tool("portainer-gitops", "list_git_credentials", {}); ``` ### Edge Stack GitOps ```javascript // Enable GitOps for edge stack await use_mcp_tool("portainer-gitops", "enable_edge_stack_gitops", { edge_stack_id: "3", mechanism: "polling", interval: "15m" }); // List all GitOps edge stacks await use_mcp_tool("portainer-gitops", "list_gitops_edge_stacks", {}); ``` ### Manual Updates ```javascript // Trigger immediate update await use_mcp_tool("portainer-gitops", "trigger_stack_update", { stack_id: "5", pull_image: true }); ``` ## GitOps Workflow ### 1. **Setup Git Repository** - Store your Docker Compose or Kubernetes manifests in Git - Use branches or tags for different environments - Configure CI/CD pipelines to update manifests ### 2. **Deploy Stack from Git** - Use the stacks server to create a stack from Git repository - Provide credentials if using a private repository ### 3. **Enable GitOps** - Choose between polling or webhook mechanism - Configure update intervals for polling - Set force update if you want Git as single source of truth ### 4. **Webhook Integration** - Add webhook URL to your Git repository - Configure to trigger on push events - Use in GitHub Actions or other CI/CD tools ### 5. **Monitor Updates** - Check GitOps status for stacks - Review update logs in Portainer - Use manual triggers for testing ## Update Mechanisms ### Polling - Portainer periodically checks Git repository for changes - Default interval: 5 minutes - Suitable for: Regular updates, less critical deployments - Pros: Simple setup, no external configuration - Cons: Delayed updates, resource usage ### Webhooks - Git repository notifies Portainer of changes - Immediate updates on push - Suitable for: CI/CD pipelines, immediate deployments - Pros: Instant updates, event-driven - Cons: Requires webhook configuration in Git ## Best Practices 1. **Use Webhooks for Production**: Faster response to changes 2. **Set Appropriate Intervals**: Balance between responsiveness and resource usage 3. **Enable Force Update Carefully**: Can overwrite local changes 4. **Secure Credentials**: Use personal access tokens, not passwords 5. **Test First**: Use manual triggers to test deployments 6. **Monitor Logs**: Check Portainer logs for update status 7. **Version Control**: Use Git tags for production deployments ## Security Considerations - Git credentials are stored encrypted in Portainer - Use personal access tokens with minimal permissions - Webhook URLs contain unique IDs for security - Enable HTTPS for webhook endpoints - Regularly rotate Git credentials - Use read-only tokens when possible ## Troubleshooting ### Common Issues 1. **Updates not triggering**: Check Git credentials and repository access 2. **Webhook failures**: Verify webhook URL and network connectivity 3. **Authentication errors**: Ensure credentials have repository access 4. **Polling delays**: Check interval settings and Portainer logs ### Debug Mode Enable debug logging by setting in your environment: ```bash DEBUG=true LOG_LEVEL=DEBUG ``` ## Requirements - Python 3.8+ - Portainer Business Edition 2.19+ (for full GitOps features) - Valid Portainer API token - Git repository with Docker Compose or Kubernetes manifests - Network access between Portainer and Git repository