mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-03 20:40:00 -05:00
567 lines
26 KiB
Markdown
567 lines
26 KiB
Markdown
|
|
# Python Plugin gRPC Architecture
|
||
|
|
|
||
|
|
This document describes the architecture of the Mattermost Python plugin system, which enables server-side plugins written in Python to have full API parity with Go plugins.
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
### Purpose
|
||
|
|
|
||
|
|
The Python plugin system extends Mattermost's existing plugin infrastructure to support plugins written in languages beyond Go. It uses gRPC with Protocol Buffers for language-agnostic communication while maintaining the subprocess-per-plugin model and seamless integration with the existing plugin infrastructure.
|
||
|
|
|
||
|
|
### Key Value Proposition
|
||
|
|
|
||
|
|
**Full API parity**: Every API method and hook available to Go plugins works identically from Python plugins. Python plugins can:
|
||
|
|
- Receive all server hooks (message events, user events, HTTP requests, etc.)
|
||
|
|
- Call all 100+ Plugin API methods (users, channels, posts, KV store, etc.)
|
||
|
|
- Handle HTTP requests via bidirectional streaming
|
||
|
|
|
||
|
|
### High-Level Architecture
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||
|
|
│ Mattermost Server │
|
||
|
|
│ │
|
||
|
|
│ ┌──────────────┐ ┌─────────────────┐ ┌────────────────────────────┐ │
|
||
|
|
│ │ App Layer │───▶│ Plugin Env │───▶│ Python Supervisor │ │
|
||
|
|
│ │ │ │ (environment.go)│ │ (python_supervisor.go) │ │
|
||
|
|
│ └──────────────┘ └─────────────────┘ └────────────────────────────┘ │
|
||
|
|
│ │ │ │
|
||
|
|
│ ▼ ▼ │
|
||
|
|
│ ┌─────────────────┐ ┌────────────────────────────┐ │
|
||
|
|
│ │ hooksGRPCClient │ │ PluginAPI gRPC Server │ │
|
||
|
|
│ │ (hook dispatch) │ │ (api_server.go) │ │
|
||
|
|
│ └────────┬────────┘ └────────────┬───────────────┘ │
|
||
|
|
│ │ │ │
|
||
|
|
└───────────────────────────────┼──────────────────────────┼──────────────────┘
|
||
|
|
│ gRPC │ gRPC
|
||
|
|
Hooks call │ │ API calls
|
||
|
|
(Server→Plugin) │ (Plugin→Server)
|
||
|
|
▼ ▼
|
||
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||
|
|
│ Python Plugin Process │
|
||
|
|
│ │
|
||
|
|
│ ┌────────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │
|
||
|
|
│ │ PluginHooks │ │ Plugin Class │ │ API Client │ │
|
||
|
|
│ │ gRPC Server │◀───│ (user code) │───▶│ (calls Go API) │ │
|
||
|
|
│ │ (server.py) │ │ (plugin.py) │ │ (client.py) │ │
|
||
|
|
│ └────────────────────┘ └─────────────────┘ └─────────────────────┘ │
|
||
|
|
│ │
|
||
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
## Component Layers
|
||
|
|
|
||
|
|
### Protocol Layer
|
||
|
|
|
||
|
|
Protocol Buffer definitions define the contract between Go and Python:
|
||
|
|
|
||
|
|
| File | Purpose |
|
||
|
|
|------|---------|
|
||
|
|
| `proto/api.proto` | Main PluginAPI service definition (100+ methods) |
|
||
|
|
| `proto/hooks.proto` | PluginHooks service definition (30+ hooks) |
|
||
|
|
| `proto/api_user_team.proto` | User and team API message types |
|
||
|
|
| `proto/api_channel_post.proto` | Channel, post, emoji message types |
|
||
|
|
| `proto/api_kv_config.proto` | KV store, config, logging message types |
|
||
|
|
| `proto/api_file_bot.proto` | File and bot message types |
|
||
|
|
| `proto/api_remaining.proto` | Server, command, preference, group, and other types |
|
||
|
|
| `proto/hooks_lifecycle.proto` | Lifecycle hook messages (OnActivate, etc.) |
|
||
|
|
| `proto/hooks_message.proto` | Message hook messages (MessageWillBePosted, etc.) |
|
||
|
|
| `proto/hooks_user_channel.proto` | User and channel hook messages |
|
||
|
|
| `proto/hooks_command.proto` | Command, WebSocket, cluster hook messages |
|
||
|
|
| `proto/hooks_http.proto` | HTTP streaming hook messages (ServeHTTP) |
|
||
|
|
| `proto/types.proto` | Shared model types (User, Post, Channel, etc.) |
|
||
|
|
|
||
|
|
### Go Infrastructure Layer
|
||
|
|
|
||
|
|
#### Python Supervisor (`server/public/plugin/python_supervisor.go`)
|
||
|
|
|
||
|
|
Manages Python plugin process lifecycle:
|
||
|
|
|
||
|
|
- **Runtime Detection**: Detects Python plugins via `manifest.Server.Runtime == "python"` or `.py` extension
|
||
|
|
- **Interpreter Discovery**: Finds Python interpreter (venv-first, then system PATH)
|
||
|
|
- **Process Spawning**: Creates exec.Cmd with proper working directory
|
||
|
|
- **API Server Startup**: Starts gRPC PluginAPI server before subprocess
|
||
|
|
- **Environment Setup**: Sets `MATTERMOST_PLUGIN_API_TARGET` env var with server address
|
||
|
|
- **Graceful Shutdown**: 5-second WaitDelay for graceful termination
|
||
|
|
|
||
|
|
Key functions:
|
||
|
|
```go
|
||
|
|
func isPythonPlugin(manifest *model.Manifest) bool
|
||
|
|
func findPythonInterpreter(pluginDir string) (string, error)
|
||
|
|
func startAPIServer(apiImpl API, registrar APIServerRegistrar) (string, func(), error)
|
||
|
|
func configurePythonCommand(pluginInfo *model.BundleInfo, ...) error
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Hooks gRPC Client (`server/public/plugin/hooks_grpc_client.go`)
|
||
|
|
|
||
|
|
Dispatches hook invocations to Python plugins:
|
||
|
|
|
||
|
|
- **Hook Registry**: Queries `Implemented()` RPC to know which hooks the plugin handles
|
||
|
|
- **Hook Dispatch**: Calls appropriate gRPC method for each hook type
|
||
|
|
- **Timeout Management**: 30-second default timeout per hook call
|
||
|
|
- **Bidirectional Streaming**: Handles ServeHTTP with request/response streaming
|
||
|
|
- **Model Conversion**: Converts between model types and proto messages
|
||
|
|
|
||
|
|
Key type:
|
||
|
|
```go
|
||
|
|
type hooksGRPCClient struct {
|
||
|
|
client pb.PluginHooksClient
|
||
|
|
implemented [TotalHooksID]bool
|
||
|
|
log *mlog.Logger
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### API Server (`server/public/pluginapi/grpc/server/api_server.go`)
|
||
|
|
|
||
|
|
gRPC server that Python plugins call back to:
|
||
|
|
|
||
|
|
- **API Wrapping**: Wraps the plugin.API interface for gRPC access
|
||
|
|
- **Method Implementations**: Each RPC delegates to corresponding API method
|
||
|
|
- **Error Conversion**: Converts model.AppError to response-embedded errors (not gRPC status)
|
||
|
|
- **Registration**: `Register(grpcServer, apiImpl)` registers the service
|
||
|
|
|
||
|
|
Key type:
|
||
|
|
```go
|
||
|
|
type APIServer struct {
|
||
|
|
pb.UnimplementedPluginAPIServer
|
||
|
|
impl plugin.API
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### ServeHTTP Handler (`server/public/pluginapi/grpc/server/serve_http.go`)
|
||
|
|
|
||
|
|
Handles bidirectional HTTP streaming:
|
||
|
|
|
||
|
|
- **64KB Chunking**: Streams request/response bodies in 64KB chunks
|
||
|
|
- **Early Response**: Plugin can respond before request body is fully sent
|
||
|
|
- **Cancellation**: HTTP client disconnect propagates via gRPC context
|
||
|
|
- **Status Code Validation**: Protects against invalid status codes (100-999 range)
|
||
|
|
- **Flush Support**: Best-effort flushing when ResponseWriter supports Flusher
|
||
|
|
|
||
|
|
Constants:
|
||
|
|
```go
|
||
|
|
const DefaultChunkSize = 64 * 1024 // 64KB
|
||
|
|
```
|
||
|
|
|
||
|
|
### Python SDK Layer
|
||
|
|
|
||
|
|
#### Plugin Base Class (`python-sdk/src/mattermost_plugin/plugin.py`)
|
||
|
|
|
||
|
|
Base class for plugin authors:
|
||
|
|
|
||
|
|
- **`__init_subclass__`**: Auto-discovers @hook decorated methods at class definition time
|
||
|
|
- **Hook Registry**: Builds class-level `_hook_registry` mapping hook names to handlers
|
||
|
|
- **API Access**: Provides `self.api` property for making API calls
|
||
|
|
- **Logging**: Provides `self.logger` for plugin logging
|
||
|
|
|
||
|
|
```python
|
||
|
|
class Plugin:
|
||
|
|
_hook_registry: Dict[str, Callable[..., Any]] = {}
|
||
|
|
|
||
|
|
def __init_subclass__(cls, **kwargs):
|
||
|
|
# Discover @hook decorated methods
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def implemented_hooks(cls) -> List[str]:
|
||
|
|
# Returns list of hook names for Implemented() RPC
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Hook System (`python-sdk/src/mattermost_plugin/hooks.py`)
|
||
|
|
|
||
|
|
Decorator-based hook registration:
|
||
|
|
|
||
|
|
- **`@hook` Decorator**: Marks methods as hook handlers
|
||
|
|
- **`HookName` Enum**: All 30+ canonical hook names matching Go
|
||
|
|
- **Validation**: Rejects unknown hook names at decoration time
|
||
|
|
- **Async Support**: Handles both sync and async handlers
|
||
|
|
|
||
|
|
```python
|
||
|
|
class HookName(str, Enum):
|
||
|
|
OnActivate = "OnActivate"
|
||
|
|
MessageWillBePosted = "MessageWillBePosted"
|
||
|
|
ServeHTTP = "ServeHTTP"
|
||
|
|
# ... 30+ hooks
|
||
|
|
|
||
|
|
@hook(HookName.OnActivate)
|
||
|
|
def on_activate(self) -> None:
|
||
|
|
pass
|
||
|
|
```
|
||
|
|
|
||
|
|
#### API Client (`python-sdk/src/mattermost_plugin/client.py`)
|
||
|
|
|
||
|
|
Typed client for calling Mattermost API:
|
||
|
|
|
||
|
|
- **Context Manager**: `with PluginAPIClient() as client:`
|
||
|
|
- **Mixin Architecture**: Organized by API domain (users, teams, channels, etc.)
|
||
|
|
- **Error Handling**: Converts gRPC errors and AppErrors to SDK exceptions
|
||
|
|
- **Type Safety**: Full type hints for all methods
|
||
|
|
|
||
|
|
```python
|
||
|
|
class PluginAPIClient(
|
||
|
|
UsersMixin,
|
||
|
|
TeamsMixin,
|
||
|
|
ChannelsMixin,
|
||
|
|
PostsMixin,
|
||
|
|
# ... more mixins
|
||
|
|
):
|
||
|
|
def get_server_version(self) -> str: ...
|
||
|
|
def get_user(self, user_id: str) -> User: ...
|
||
|
|
```
|
||
|
|
|
||
|
|
#### gRPC Server (`python-sdk/src/mattermost_plugin/server.py`)
|
||
|
|
|
||
|
|
Plugin-side gRPC server for receiving hooks:
|
||
|
|
|
||
|
|
- **Async Server**: Uses grpc.aio for async gRPC
|
||
|
|
- **Health Service**: Registers grpc.health.v1.Health for go-plugin
|
||
|
|
- **Handshake**: Outputs go-plugin handshake line to stdout
|
||
|
|
- **Signal Handling**: Graceful shutdown on SIGTERM/SIGINT
|
||
|
|
|
||
|
|
```python
|
||
|
|
async def serve_plugin(plugin_class: Type[Plugin]) -> None:
|
||
|
|
# 1. Load runtime config from environment
|
||
|
|
# 2. Create and connect API client
|
||
|
|
# 3. Instantiate plugin
|
||
|
|
# 4. Start gRPC server with health service
|
||
|
|
# 5. Output handshake line
|
||
|
|
# 6. Wait for termination
|
||
|
|
```
|
||
|
|
|
||
|
|
## Process Lifecycle
|
||
|
|
|
||
|
|
### Plugin Loading Sequence
|
||
|
|
|
||
|
|
```
|
||
|
|
┌──────────────────┐
|
||
|
|
│ 1. Manifest Parse│ Server reads plugin.json, detects runtime="python"
|
||
|
|
└────────┬─────────┘
|
||
|
|
▼
|
||
|
|
┌──────────────────┐
|
||
|
|
│ 2. API Server │ Start gRPC PluginAPI server on random port
|
||
|
|
│ Startup │ Store cleanup function for shutdown
|
||
|
|
└────────┬─────────┘
|
||
|
|
▼
|
||
|
|
┌──────────────────┐
|
||
|
|
│ 3. Python Process│ python_supervisor.go builds exec.Cmd:
|
||
|
|
│ Spawn │ - Find Python interpreter (venv-first)
|
||
|
|
│ │ - Set MATTERMOST_PLUGIN_API_TARGET env var
|
||
|
|
│ │ - Set working directory to plugin path
|
||
|
|
└────────┬─────────┘
|
||
|
|
▼
|
||
|
|
┌──────────────────┐
|
||
|
|
│ 4. go-plugin │ Wait for handshake line on stdout:
|
||
|
|
│ Handshake │ "1|1|tcp|127.0.0.1:PORT|grpc"
|
||
|
|
│ │ Health check confirms plugin is serving
|
||
|
|
└────────┬─────────┘
|
||
|
|
▼
|
||
|
|
┌──────────────────┐
|
||
|
|
│ 5. Implemented() │ Query which hooks the plugin implements
|
||
|
|
│ RPC │ Populate implemented[] array for optimization
|
||
|
|
└────────┬─────────┘
|
||
|
|
▼
|
||
|
|
┌──────────────────┐
|
||
|
|
│ 6. OnActivate() │ Call OnActivate hook if implemented
|
||
|
|
│ Hook │ Plugin can initialize, register commands
|
||
|
|
└────────┬─────────┘
|
||
|
|
▼
|
||
|
|
┌──────────────────┐
|
||
|
|
│ 7. Running │ Plugin is now active and receiving hooks
|
||
|
|
└──────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
### Environment Variables
|
||
|
|
|
||
|
|
| Variable | Set By | Used By | Purpose |
|
||
|
|
|----------|--------|---------|---------|
|
||
|
|
| `MATTERMOST_PLUGIN_API_TARGET` | Go Supervisor | Python Client | gRPC address for API calls |
|
||
|
|
| `MATTERMOST_PLUGIN_ID` | Go Supervisor | Python SDK | Plugin identifier |
|
||
|
|
| `MATTERMOST_LOG_LEVEL` | Go Supervisor | Python SDK | Logging level configuration |
|
||
|
|
|
||
|
|
### Shutdown Sequence
|
||
|
|
|
||
|
|
```
|
||
|
|
┌──────────────────┐
|
||
|
|
│ 1. OnDeactivate()│ Server calls OnDeactivate hook
|
||
|
|
└────────┬─────────┘
|
||
|
|
▼
|
||
|
|
┌──────────────────┐
|
||
|
|
│ 2. gRPC Client │ Close gRPC connection to plugin
|
||
|
|
│ Close │
|
||
|
|
└────────┬─────────┘
|
||
|
|
▼
|
||
|
|
┌──────────────────┐
|
||
|
|
│ 3. Process │ Send termination signal, wait 5 seconds
|
||
|
|
│ Termination │ (cmd.WaitDelay = 5 * time.Second)
|
||
|
|
└────────┬─────────┘
|
||
|
|
▼
|
||
|
|
┌──────────────────┐
|
||
|
|
│ 4. API Server │ Call apiServerCleanup() for graceful stop
|
||
|
|
│ Shutdown │ GracefulStop() drains pending requests
|
||
|
|
└──────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
## Communication Flow
|
||
|
|
|
||
|
|
### Hook Dispatch (Server to Plugin)
|
||
|
|
|
||
|
|
```
|
||
|
|
Go Server Python Plugin
|
||
|
|
│ │
|
||
|
|
│ ╔═══════════════════════════════════════╗ │
|
||
|
|
│ ║ Hook Event (e.g., MessageWillBePosted) ║ │
|
||
|
|
│ ╚═══════════════════════════════════════╝ │
|
||
|
|
│ │
|
||
|
|
├──────────────────────────────────────────────►
|
||
|
|
│ gRPC: MessageWillBePosted │
|
||
|
|
│ (context, post proto) │
|
||
|
|
│ │
|
||
|
|
│ ┌────────┤
|
||
|
|
│ │ Plugin │
|
||
|
|
│ │ @hook │
|
||
|
|
│ │ handler│
|
||
|
|
│ └────────┤
|
||
|
|
│ │
|
||
|
|
◄──────────────────────────────────────────────┤
|
||
|
|
│ Response │
|
||
|
|
│ (modified_post, rejection_reason) │
|
||
|
|
│ │
|
||
|
|
```
|
||
|
|
|
||
|
|
### API Call (Plugin to Server)
|
||
|
|
|
||
|
|
```
|
||
|
|
Python Plugin Go Server
|
||
|
|
│ │
|
||
|
|
│ ╔═══════════════════════════════════════╗ │
|
||
|
|
│ ║ Plugin code: self.api.get_user(id) ║ │
|
||
|
|
│ ╚═══════════════════════════════════════╝ │
|
||
|
|
│ │
|
||
|
|
├──────────────────────────────────────────────►
|
||
|
|
│ gRPC: GetUser │
|
||
|
|
│ (user_id) │
|
||
|
|
│ │
|
||
|
|
│ ┌────────┤
|
||
|
|
│ │ API │
|
||
|
|
│ │ Server │
|
||
|
|
│ │ impl │
|
||
|
|
│ └────────┤
|
||
|
|
│ │
|
||
|
|
◄──────────────────────────────────────────────┤
|
||
|
|
│ Response │
|
||
|
|
│ (user proto, error) │
|
||
|
|
│ │
|
||
|
|
```
|
||
|
|
|
||
|
|
### ServeHTTP Bidirectional Streaming
|
||
|
|
|
||
|
|
```
|
||
|
|
Go Server Python Plugin
|
||
|
|
│ │
|
||
|
|
│ ╔═══════════════════════════════════════╗ │
|
||
|
|
│ ║ HTTP Request to /plugins/{id}/... ║ │
|
||
|
|
│ ╚═══════════════════════════════════════╝ │
|
||
|
|
│ │
|
||
|
|
│ ════════ Bidirectional Stream ════════ │
|
||
|
|
│ │
|
||
|
|
├───────────────────────────────────────────────►
|
||
|
|
│ Request Init (method, URL, headers) │
|
||
|
|
│ + first body chunk │
|
||
|
|
│ │
|
||
|
|
├───────────────────────────────────────────────►
|
||
|
|
│ Body chunk 2 (64KB) │
|
||
|
|
│ │
|
||
|
|
├───────────────────────────────────────────────►
|
||
|
|
│ Body chunk 3 + body_complete=true │
|
||
|
|
│ │
|
||
|
|
│ ┌────────┤
|
||
|
|
│ │ @hook │
|
||
|
|
│ │ServeHTTP│
|
||
|
|
│ └────────┤
|
||
|
|
│ │
|
||
|
|
◄───────────────────────────────────────────────┤
|
||
|
|
│ Response Init (status=200, headers) │
|
||
|
|
│ + first response chunk │
|
||
|
|
│ │
|
||
|
|
◄───────────────────────────────────────────────┤
|
||
|
|
│ Response body chunk + body_complete=true │
|
||
|
|
│ │
|
||
|
|
```
|
||
|
|
|
||
|
|
## Key Design Decisions
|
||
|
|
|
||
|
|
### Response-Embedded AppError (Not gRPC Status)
|
||
|
|
|
||
|
|
**Decision**: API errors are encoded in the response message's `error` field, not as gRPC status codes.
|
||
|
|
|
||
|
|
**Rationale**:
|
||
|
|
- Preserves full AppError semantics (id, where, detailed_error, status_code, params)
|
||
|
|
- gRPC status codes are reserved for transport-level failures only
|
||
|
|
- Matches existing Go plugin error handling patterns
|
||
|
|
|
||
|
|
**Example**:
|
||
|
|
```protobuf
|
||
|
|
message GetUserResponse {
|
||
|
|
AppError error = 1; // Application-level error
|
||
|
|
User user = 2; // Success response
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 64KB Streaming Chunks
|
||
|
|
|
||
|
|
**Decision**: HTTP request/response bodies are streamed in 64KB chunks.
|
||
|
|
|
||
|
|
**Rationale**:
|
||
|
|
- Matches gRPC best practices for streaming
|
||
|
|
- Avoids buffering entire request bodies in memory
|
||
|
|
- Enables early response (plugin can respond before request fully received)
|
||
|
|
|
||
|
|
**Constant**: `serveHTTPChunkSize = 64 * 1024`
|
||
|
|
|
||
|
|
### JSON Blobs for Complex Types
|
||
|
|
|
||
|
|
**Decision**: Some complex types (Config, License, Manifest) are serialized as JSON blobs.
|
||
|
|
|
||
|
|
**Rationale**:
|
||
|
|
- These types have deeply nested structures that would be verbose in proto
|
||
|
|
- Reduces proto definition maintenance burden
|
||
|
|
- Types are used infrequently, so serialization overhead is acceptable
|
||
|
|
|
||
|
|
**Example**:
|
||
|
|
```protobuf
|
||
|
|
message ConfigJson {
|
||
|
|
bytes config_json = 1; // JSON-serialized model.Config
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### APIServerRegistrar Pattern
|
||
|
|
|
||
|
|
**Decision**: Use a function type to break import cycle between plugin and pluginapi/grpc/server.
|
||
|
|
|
||
|
|
**Rationale**:
|
||
|
|
- `plugin` package cannot import `pluginapi/grpc/server` (circular dependency)
|
||
|
|
- The registrar function is passed in at runtime
|
||
|
|
- Pattern: `type APIServerRegistrar func(grpcServer *grpc.Server, apiImpl API)`
|
||
|
|
|
||
|
|
**Usage** (in app layer):
|
||
|
|
```go
|
||
|
|
env.SetAPIServerRegistrar(func(s *grpc.Server, api plugin.API) {
|
||
|
|
server.Register(s, api)
|
||
|
|
})
|
||
|
|
```
|
||
|
|
|
||
|
|
### Venv-First Interpreter Discovery
|
||
|
|
|
||
|
|
**Decision**: Look for Python in plugin's venv before system PATH.
|
||
|
|
|
||
|
|
**Rationale**:
|
||
|
|
- Plugins can bundle their dependencies in a venv
|
||
|
|
- Avoids conflicts with system Python or other plugins
|
||
|
|
- Search order: `venv/bin/python` → `.venv/bin/python` → `python3` → `python`
|
||
|
|
|
||
|
|
### go-plugin Protocol
|
||
|
|
|
||
|
|
**Decision**: Use HashiCorp's go-plugin library for process management.
|
||
|
|
|
||
|
|
**Rationale**:
|
||
|
|
- Battle-tested subprocess management
|
||
|
|
- Health checking via grpc.health.v1
|
||
|
|
- Secure handshake protocol
|
||
|
|
- Automatic connection management
|
||
|
|
|
||
|
|
**Handshake Format**: `CORE-VERSION|APP-VERSION|NETWORK|ADDRESS|PROTOCOL`
|
||
|
|
- Example: `1|1|tcp|127.0.0.1:54321|grpc`
|
||
|
|
|
||
|
|
## File Reference
|
||
|
|
|
||
|
|
### Go Server Components
|
||
|
|
|
||
|
|
| Functionality | File Path |
|
||
|
|
|--------------|-----------|
|
||
|
|
| Plugin environment | `server/public/plugin/environment.go` |
|
||
|
|
| Python supervisor | `server/public/plugin/python_supervisor.go` |
|
||
|
|
| Hooks gRPC client | `server/public/plugin/hooks_grpc_client.go` |
|
||
|
|
| Hook ID constants | `server/public/plugin/hooks.go` |
|
||
|
|
| Supervisor base | `server/public/plugin/supervisor.go` |
|
||
|
|
| API Server | `server/public/pluginapi/grpc/server/api_server.go` |
|
||
|
|
| ServeHTTP handler | `server/public/pluginapi/grpc/server/serve_http.go` |
|
||
|
|
|
||
|
|
### Go API Implementation Files
|
||
|
|
|
||
|
|
| Functionality | File Path |
|
||
|
|
|--------------|-----------|
|
||
|
|
| User/Team APIs | `server/public/pluginapi/grpc/server/api_user_team.go` |
|
||
|
|
| Channel/Post APIs | `server/public/pluginapi/grpc/server/api_channel_post.go` |
|
||
|
|
| KV/Config APIs | `server/public/pluginapi/grpc/server/api_kv_config.go` |
|
||
|
|
| File/Bot APIs | `server/public/pluginapi/grpc/server/api_file_bot.go` |
|
||
|
|
| Remaining APIs | `server/public/pluginapi/grpc/server/api_remaining.go` |
|
||
|
|
|
||
|
|
### Proto Definitions
|
||
|
|
|
||
|
|
| Functionality | File Path |
|
||
|
|
|--------------|-----------|
|
||
|
|
| Main API service | `server/public/pluginapi/grpc/proto/api.proto` |
|
||
|
|
| Main hooks service | `server/public/pluginapi/grpc/proto/hooks.proto` |
|
||
|
|
| Shared types | `server/public/pluginapi/grpc/proto/types.proto` |
|
||
|
|
| User/Team messages | `server/public/pluginapi/grpc/proto/api_user_team.proto` |
|
||
|
|
| Channel/Post messages | `server/public/pluginapi/grpc/proto/api_channel_post.proto` |
|
||
|
|
| KV/Config messages | `server/public/pluginapi/grpc/proto/api_kv_config.proto` |
|
||
|
|
| File/Bot messages | `server/public/pluginapi/grpc/proto/api_file_bot.proto` |
|
||
|
|
| Remaining messages | `server/public/pluginapi/grpc/proto/api_remaining.proto` |
|
||
|
|
| Lifecycle hooks | `server/public/pluginapi/grpc/proto/hooks_lifecycle.proto` |
|
||
|
|
| Message hooks | `server/public/pluginapi/grpc/proto/hooks_message.proto` |
|
||
|
|
| User/Channel hooks | `server/public/pluginapi/grpc/proto/hooks_user_channel.proto` |
|
||
|
|
| Command hooks | `server/public/pluginapi/grpc/proto/hooks_command.proto` |
|
||
|
|
| HTTP hooks | `server/public/pluginapi/grpc/proto/hooks_http.proto` |
|
||
|
|
|
||
|
|
### Python SDK Components
|
||
|
|
|
||
|
|
| Functionality | File Path |
|
||
|
|
|--------------|-----------|
|
||
|
|
| Plugin base class | `python-sdk/src/mattermost_plugin/plugin.py` |
|
||
|
|
| Hook decorator | `python-sdk/src/mattermost_plugin/hooks.py` |
|
||
|
|
| API client | `python-sdk/src/mattermost_plugin/client.py` |
|
||
|
|
| gRPC server | `python-sdk/src/mattermost_plugin/server.py` |
|
||
|
|
| Runtime config | `python-sdk/src/mattermost_plugin/runtime_config.py` |
|
||
|
|
| Hook servicer | `python-sdk/src/mattermost_plugin/servicers/hooks_servicer.py` |
|
||
|
|
| Channel utilities | `python-sdk/src/mattermost_plugin/_internal/channel.py` |
|
||
|
|
| API mixins | `python-sdk/src/mattermost_plugin/_internal/mixins/*.py` |
|
||
|
|
| Exception types | `python-sdk/src/mattermost_plugin/exceptions.py` |
|
||
|
|
|
||
|
|
### Generated Code
|
||
|
|
|
||
|
|
| Language | Path |
|
||
|
|
|----------|------|
|
||
|
|
| Go | `server/public/pluginapi/grpc/generated/go/pluginapiv1/` |
|
||
|
|
| Python | `python-sdk/src/mattermost_plugin/grpc/` |
|
||
|
|
|
||
|
|
## Extending the System
|
||
|
|
|
||
|
|
### Adding a New API Method
|
||
|
|
|
||
|
|
1. Add RPC to appropriate `.proto` file
|
||
|
|
2. Run `make generate-grpc` to regenerate code
|
||
|
|
3. Implement method in Go API server (`server/api_*.go`)
|
||
|
|
4. Implement method in Python client mixin (`_internal/mixins/*.py`)
|
||
|
|
5. Add tests for both Go and Python implementations
|
||
|
|
|
||
|
|
### Adding a New Hook
|
||
|
|
|
||
|
|
1. Add RPC to `hooks.proto`
|
||
|
|
2. Add message types to appropriate `hooks_*.proto`
|
||
|
|
3. Run `make generate-grpc` to regenerate code
|
||
|
|
4. Implement dispatch in `hooks_grpc_client.go`
|
||
|
|
5. Add to `HookName` enum in Python (`hooks.py`)
|
||
|
|
6. Handle in `PluginHooksServicerImpl` (`servicers/hooks_servicer.py`)
|
||
|
|
7. Add tests
|
||
|
|
|
||
|
|
### Adding a New Model Type
|
||
|
|
|
||
|
|
1. Add message definition to `types.proto`
|
||
|
|
2. Add conversion functions in Go (`hooks_grpc_client.go` or `server/api_*.go`)
|
||
|
|
3. Add Python dataclass in `python-sdk/src/mattermost_plugin/models/`
|
||
|
|
4. Add proto-to-model conversion in Python
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*This architecture documentation is intended for internal Mattermost engineers working on the Python plugin infrastructure.*
|