Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions deploy/k8m-ms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -153,19 +153,19 @@ spec:
spec:
containers:
- name: k8m
# image: docker.io/weibh/k8m:v0.26.3
# image: ghcr.io/weibaohui/k8m:v0.26.3
image: registry.cn-hangzhou.aliyuncs.com/minik8m/k8m:v0.26.3
# image: docker.io/weibh/k8m:v0.26.6
# image: ghcr.io/weibaohui/k8m:v0.26.6
image: registry.cn-hangzhou.aliyuncs.com/minik8m/k8m:v0.26.6
env:
- name: POD_NAME #启用主备选举插件必须填,否则不会切换
- name: POD_NAME #启用主备选举插件必须填,否则不会切换
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE #启用主备选举插件必须填,否则不会切换
- name: POD_NAMESPACE #启用主备选举插件必须填,否则不会切换
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP #启用主备选举插件必须填,否则不会切换
- name: POD_IP #启用主备选举插件必须填,否则不会切换
valueFrom:
fieldRef:
fieldPath: status.podIP
Expand Down
12 changes: 6 additions & 6 deletions deploy/k8m.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -153,19 +153,19 @@ spec:
spec:
containers:
- name: k8m
# image: docker.io/weibh/k8m:v0.26.3
# image: ghcr.io/weibaohui/k8m:v0.26.3
image: registry.cn-hangzhou.aliyuncs.com/minik8m/k8m:v0.26.3
# image: docker.io/weibh/k8m:v0.26.6
# image: ghcr.io/weibaohui/k8m:v0.26.6
image: registry.cn-hangzhou.aliyuncs.com/minik8m/k8m:v0.26.6
env:
- name: POD_NAME #启用主备选举插件必须填,否则不会切换,单实例运行可以加不影响。
- name: POD_NAME #启用主备选举插件必须填,否则不会切换,单实例运行可以加不影响。
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE #启用主备选举插件必须填,否则不会切换,单实例运行可以加不影响。
- name: POD_NAMESPACE #启用主备选举插件必须填,否则不会切换,单实例运行可以加不影响。
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP #启用主备选举插件必须填,否则不会切换,单实例运行可以加不影响。
- name: POD_IP #启用主备选举插件必须填,否则不会切换,单实例运行可以加不影响。
valueFrom:
fieldRef:
fieldPath: status.podIP
Expand Down
Binary file added images/readme.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
116 changes: 71 additions & 45 deletions pkg/plugins/manager_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,22 +283,82 @@ func (m *Manager) InstallPlugin(c *response.Context) {
amis.WriteJsonOKMsg(c, "已安装")
}

// ensurePluginEnabledAndRunning 确保插件已安装、已启用且已启动
// 该方法用于多个管理接口复用:若未安装则先安装;若未启用则先启用;若未启动则启动。
func (m *Manager) ensurePluginEnabledAndRunning(name string, params *dao.Params) error {
st, ok := m.StatusOf(name)
if !ok || st == StatusUninstalled {
klog.V(6).Infof("插件未安装,先进行安装: %s", name)
if err := m.Install(name); err != nil {
return err
}
if err := m.PersistStatus(name, StatusInstalled, params); err != nil {
return err
}
st = StatusInstalled
}

if st == StatusInstalled || st == StatusDisabled {
klog.V(6).Infof("插件未启用,先进行启用: %s", name)
if err := m.Enable(name); err != nil {
return err
}
if err := m.PersistStatus(name, StatusEnabled, params); err != nil {
return err
}
st = StatusEnabled
}

if st == StatusEnabled || st == StatusStopped {
klog.V(6).Infof("插件未启动,进行启动: %s", name)
if err := m.StartPlugin(name); err != nil {
return err
}
if err := m.PersistStatus(name, StatusRunning, params); err != nil {
return err
}
}

return nil
}

// ensurePluginDisabled 确保插件处于禁用状态
// 该方法用于多个管理接口复用:若插件正在运行则先停止,再执行禁用。
func (m *Manager) ensurePluginDisabled(name string, params *dao.Params) error {
st, _ := m.StatusOf(name)
if st == StatusRunning {
klog.V(6).Infof("插件正在运行,先停止: %s", name)
if err := m.StopPlugin(name); err != nil {
return err
}
if err := m.PersistStatus(name, StatusStopped, params); err != nil {
return err
}
}

if err := m.Disable(name); err != nil {
return err
}
if err := m.PersistStatus(name, StatusDisabled, params); err != nil {
return err
}

return nil
}

// EnablePlugin 启用指定名称的插件
// 路径参数为插件名,启用失败时返回错误
func (m *Manager) EnablePlugin(c *response.Context) {
name := c.Param("name")
klog.V(6).Infof("启用插件配置请求: %s", name)
if err := m.Enable(name); err != nil {
amis.WriteJsonError(c, err)
return
}

params := dao.BuildParams(c)
if err := m.PersistStatus(name, StatusEnabled, params); err != nil {
if err := m.ensurePluginEnabledAndRunning(name, params); err != nil {
amis.WriteJsonError(c, err)
return
}

amis.WriteJsonOKMsg(c, "已启用")
amis.WriteJsonOKMsg(c, "已启用并启动")
}

// StartPluginAPI 启动指定名称的插件
Expand Down Expand Up @@ -378,12 +438,9 @@ func (m *Manager) UninstallPluginKeepData(c *response.Context) {
func (m *Manager) DisablePlugin(c *response.Context) {
name := c.Param("name")
klog.V(6).Infof("禁用插件配置请求: %s", name)
if err := m.Disable(name); err != nil {
amis.WriteJsonError(c, err)
return
}

params := dao.BuildParams(c)
if err := m.PersistStatus(name, StatusDisabled, params); err != nil {
if err := m.ensurePluginDisabled(name, params); err != nil {
amis.WriteJsonError(c, err)
return
}
Expand Down Expand Up @@ -623,22 +680,10 @@ func (m *Manager) TogglePluginEnabled(c *response.Context) {
return
}

m.mu.RLock()
currentStatus := m.status[name]
m.mu.RUnlock()
params := dao.BuildParams(c)

if enabled == "true" || enabled == "1" || enabled == "yes" {
// 启用插件:已安装或已禁用状态可以启用
if currentStatus != StatusInstalled && currentStatus != StatusDisabled {
amis.WriteJsonError(c, fmt.Errorf("插件当前状态不允许启用: %s", statusToCN(currentStatus)))
return
}
if err := m.Enable(name); err != nil {
amis.WriteJsonError(c, err)
return
}
params := dao.BuildParams(c)
if err := m.PersistStatus(name, StatusEnabled, params); err != nil {
if err := m.ensurePluginEnabledAndRunning(name, params); err != nil {
amis.WriteJsonError(c, err)
return
}
Expand All @@ -647,26 +692,7 @@ func (m *Manager) TogglePluginEnabled(c *response.Context) {
return
}

// 禁用插件:如果正在运行,先停止再禁用
if currentStatus == StatusRunning {
// 先停止插件
if err := m.StopPlugin(name); err != nil {
amis.WriteJsonError(c, fmt.Errorf("停止插件失败: %w", err))
return
}
klog.V(6).Infof("插件已停止: %s", name)
}
// 已停止/已启用/运行中 状态可以禁用
if currentStatus != StatusStopped && currentStatus != StatusRunning && currentStatus != StatusEnabled {
amis.WriteJsonError(c, fmt.Errorf("插件当前状态不允许禁用: %s", statusToCN(currentStatus)))
return
}
if err := m.Disable(name); err != nil {
amis.WriteJsonError(c, err)
return
}
params := dao.BuildParams(c)
if err := m.PersistStatus(name, StatusDisabled, params); err != nil {
if err := m.ensurePluginDisabled(name, params); err != nil {
amis.WriteJsonError(c, err)
return
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/plugins/modules/inspection/frontend/schedule.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"name": "webhooks",
"label": "Webhook",
"multiple": true,
"source": "/admin/plugins/plugins/webhook/option_list",
"source": "/admin/plugins/webhook/option_list",
"labelField": "label",
"valueField": "value",
"placeholder": "请选择目标Webhook"
Expand Down
13 changes: 12 additions & 1 deletion pkg/plugins/modules/k8m_mcp_server/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ package k8m_mcp_server

import (
"github.com/weibaohui/k8m/pkg/plugins"
"github.com/weibaohui/k8m/pkg/plugins/modules/k8m_mcp_server/models"
"k8s.io/klog/v2"
)

type K8mMcpServerLifecycle struct{}

func (k *K8mMcpServerLifecycle) Install(ctx plugins.InstallContext) error {
klog.V(6).Infof("开始安装K8M MCP Server插件")
klog.V(6).Infof("安装K8M MCP Server插件成功")
if err := models.InitDB(); err != nil {
klog.V(6).Infof("安装 K8M MCP Server 插件失败: %v", err)
return err
}
klog.V(6).Infof("安装 K8M MCP Server 插件成功")
return nil
}

Expand All @@ -30,6 +35,12 @@ func (k *K8mMcpServerLifecycle) Disable(ctx plugins.BaseContext) error {

func (k *K8mMcpServerLifecycle) Uninstall(ctx plugins.UninstallContext) error {
klog.V(6).Infof("开始卸载K8M MCP Server插件")
if !ctx.KeepData() {
if err := models.DropDB(); err != nil {
klog.V(6).Infof("卸载 K8M MCP Server 插件失败: %v", err)
return err
}
}
klog.V(6).Infof("卸载K8M MCP Server插件成功")
return nil
}
Expand Down
104 changes: 104 additions & 0 deletions pkg/plugins/modules/k8m_mcp_server/models/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package models

import (
"errors"
"fmt"

"github.com/weibaohui/k8m/internal/dao"
"github.com/weibaohui/k8m/pkg/flag"
"github.com/weibaohui/k8m/pkg/plugins"
"github.com/weibaohui/k8m/pkg/plugins/modules"
rm "github.com/weibaohui/k8m/pkg/plugins/modules/mcp_runtime/models"
"github.com/weibaohui/k8m/pkg/plugins/modules/mcp_runtime/service"
"gorm.io/gorm"
"k8s.io/klog/v2"
)

const innerMCPServerName = "k8m"

// InitDB 初始化本插件安装时需要写入的数据库数据。
func InitDB() error {
if plugins.ManagerInstance().IsRunning(modules.PluginNameMCPRuntime) {
if err := addInnerMCPServer(); err != nil {
klog.V(6).Infof("初始化内置 MCP Server 配置失败: %v", err)
return err
}
}
return nil
}

// addInnerMCPServer 检查并初始化名为 "k8m" 的内部 MCP 服务器配置,不存在则创建,已存在则更新其 URL。
func addInnerMCPServer() error {
// 检查是否存在名为k8m的记录
var count int64
if err := dao.DB().Model(&rm.MCPServerConfig{}).Where("name = ?", innerMCPServerName).Count(&count).Error; err != nil {
klog.Errorf("查询MCP服务器配置失败: %v", err)
return err
}
cfg := flag.Init()
// 如果不存在,添加默认的内部MCP服务器配置
if count == 0 {
config := &rm.MCPServerConfig{
Name: innerMCPServerName,
URL: fmt.Sprintf("http://localhost:%d/mcp/k8m/sse", cfg.Port),
Enabled: false,
}
if err := dao.DB().Create(config).Error; err != nil {
klog.Errorf("添加内部MCP服务器配置失败: %v", err)
return err
}
klog.V(4).Info("成功添加内部MCP服务器配置")
} else {
klog.V(4).Info("内部MCP服务器配置已存在")
dao.DB().Model(&rm.MCPServerConfig{}).Select("url").
Where("name =?", innerMCPServerName).
Update("url", fmt.Sprintf("http://localhost:%d/mcp/k8m/sse", cfg.Port))
}

return nil
}

// DropDB 在卸载本插件且不保留数据时,删除内置 MCP Server 配置及相关数据。
func DropDB() error {
db := dao.DB()

if !db.Migrator().HasTable(&rm.MCPServerConfig{}) {
klog.V(6).Infof("未发现 MCP Server 配置表,跳过删除内置服务器配置[%s]", innerMCPServerName)
return nil
}

var server rm.MCPServerConfig
if err := db.Where("name = ?", innerMCPServerName).First(&server).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
klog.V(6).Infof("未找到内置 MCP Server 配置[%s],无需删除", innerMCPServerName)
return nil
}
klog.V(6).Infof("查询内置 MCP Server 配置失败[%s]: %v", innerMCPServerName, err)
return err
}

if db.Migrator().HasTable(&rm.MCPTool{}) {
if err := db.Where("server_name = ?", innerMCPServerName).Delete(&rm.MCPTool{}).Error; err != nil {
klog.V(6).Infof("删除内置 MCP Server 工具记录失败[%s]: %v", innerMCPServerName, err)
return err
}
}
if db.Migrator().HasTable(&rm.MCPToolLog{}) {
if err := db.Where("server_name = ?", innerMCPServerName).Delete(&rm.MCPToolLog{}).Error; err != nil {
klog.V(6).Infof("删除内置 MCP Server 工具日志失败[%s]: %v", innerMCPServerName, err)
return err
}
}

if err := db.Where("name = ?", innerMCPServerName).Delete(&rm.MCPServerConfig{}).Error; err != nil {
klog.V(6).Infof("删除内置 MCP Server 配置失败[%s]: %v", innerMCPServerName, err)
return err
}

if plugins.ManagerInstance().IsRunning(modules.PluginNameMCPRuntime) {
service.McpService().RemoveServer(server)
}

klog.V(6).Infof("已删除内置 MCP Server 配置及相关数据[%s]", innerMCPServerName)
return nil
}
Loading
Loading