|
@@ -15,6 +15,7 @@ import (
|
|
|
"strings"
|
|
"strings"
|
|
|
|
|
|
|
|
"udeServer/internal/apierr"
|
|
"udeServer/internal/apierr"
|
|
|
|
|
+ "udeServer/internal/audit"
|
|
|
"udeServer/internal/model"
|
|
"udeServer/internal/model"
|
|
|
"udeServer/internal/svc"
|
|
"udeServer/internal/svc"
|
|
|
"udeServer/internal/types"
|
|
"udeServer/internal/types"
|
|
@@ -164,8 +165,7 @@ func (l *UpdateServiceLogic) applyMountChanges(svc *model.TService, oldDirs, new
|
|
|
msgs = append(msgs, "原外挂 IP/端口为空,无法联动卸载,旧目录可能仍处于挂载状态")
|
|
msgs = append(msgs, "原外挂 IP/端口为空,无法联动卸载,旧目录可能仍处于挂载状态")
|
|
|
} else {
|
|
} else {
|
|
|
for _, dir := range toUnmount {
|
|
for _, dir := range toUnmount {
|
|
|
- // X-TOKEN 用【旧】iden:卸载发往旧机器,旧机器 TOKEN 认的还是改前的接入标识
|
|
|
|
|
- if w := l.fuseUnmountOne(oldIp, oldPort, dir, svc.Iden.String); w != "" {
|
|
|
|
|
|
|
+ if w := l.fuseUnmountOne(oldIp, oldPort, dir); w != "" {
|
|
|
msgs = append(msgs, w)
|
|
msgs = append(msgs, w)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -182,7 +182,7 @@ func (l *UpdateServiceLogic) applyMountChanges(svc *model.TService, oldDirs, new
|
|
|
msgs = append(msgs, fmt.Sprintf("联动挂载失败: %v", err))
|
|
msgs = append(msgs, fmt.Sprintf("联动挂载失败: %v", err))
|
|
|
} else {
|
|
} else {
|
|
|
for _, dir := range newDirs {
|
|
for _, dir := range newDirs {
|
|
|
- if w := l.fuseMountOne(req.Ip, req.Port, dir, ikey, req.Iden); w != "" {
|
|
|
|
|
|
|
+ if w := l.fuseMountOne(req.Ip, req.Port, dir, ikey); w != "" {
|
|
|
msgs = append(msgs, w)
|
|
msgs = append(msgs, w)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -209,7 +209,7 @@ func (l *UpdateServiceLogic) applyMountChanges(svc *model.TService, oldDirs, new
|
|
|
|
|
|
|
|
// fuseMountOne 通知外挂程序挂载单个目录。成功返回空串,失败返回警告文案。
|
|
// fuseMountOne 通知外挂程序挂载单个目录。成功返回空串,失败返回警告文案。
|
|
|
// 请求体与新增服务完全同款:{backing: 目录+"-cipher", mount: 目录, ikey: 密钥内容}
|
|
// 请求体与新增服务完全同款:{backing: 目录+"-cipher", mount: 目录, ikey: 密钥内容}
|
|
|
-func (l *UpdateServiceLogic) fuseMountOne(ip string, port int64, mount, ikey, iden string) string {
|
|
|
|
|
|
|
+func (l *UpdateServiceLogic) fuseMountOne(ip string, port int64, mount, ikey string) string {
|
|
|
// path.Clean(非 filepath!):路径要发给远程 Linux 外挂程序,必须保持 / 分隔符
|
|
// path.Clean(非 filepath!):路径要发给远程 Linux 外挂程序,必须保持 / 分隔符
|
|
|
dirLinuxPath := path.Clean(mount)
|
|
dirLinuxPath := path.Clean(mount)
|
|
|
payload := map[string]interface{}{
|
|
payload := map[string]interface{}{
|
|
@@ -222,24 +222,30 @@ func (l *UpdateServiceLogic) fuseMountOne(ip string, port int64, mount, ikey, id
|
|
|
return fmt.Sprintf("序列化挂载请求失败: %v", err)
|
|
return fmt.Sprintf("序列化挂载请求失败: %v", err)
|
|
|
}
|
|
}
|
|
|
baseURL := fmt.Sprintf("http://%s:%d/api/fuseMount", ip, port) // %d:port 是 int64
|
|
baseURL := fmt.Sprintf("http://%s:%d/api/fuseMount", ip, port) // %d:port 是 int64
|
|
|
- resp, err := doFuseRequest(fuseMountClient, iden, http.MethodPost, baseURL, bytes.NewReader(body), "application/json")
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return fmt.Sprintf("挂载目录 %s 失败(外挂程序未启动?): %v", dirLinuxPath, err)
|
|
|
|
|
- }
|
|
|
|
|
- if resp.StatusCode >= http.StatusBadRequest {
|
|
|
|
|
- snippet, _ := io.ReadAll(io.LimitReader(resp.Body, 256)) // 带出外挂错误内容(前 256 字节)
|
|
|
|
|
- resp.Body.Close()
|
|
|
|
|
- return fmt.Sprintf("挂载目录 %s 失败,外挂程序返回 HTTP %d: %s", dirLinuxPath, resp.StatusCode, string(snippet))
|
|
|
|
|
|
|
+ // DoSimple 记录 type=2 接口日志,父 Span 指向本次入站请求
|
|
|
|
|
+ if _, callErr := audit.DoSimple(l.svcCtx.SpanRecorder, l.ctx, "FuseMount", baseURL, body,
|
|
|
|
|
+ func() (map[string]interface{}, int64, error) {
|
|
|
|
|
+ resp, rerr := fuseMountClient.Post(baseURL, "application/json", bytes.NewReader(body))
|
|
|
|
|
+ if rerr != nil {
|
|
|
|
|
+ return nil, 0, fmt.Errorf("挂载目录 %s 失败(外挂程序未启动?): %w", dirLinuxPath, rerr)
|
|
|
|
|
+ }
|
|
|
|
|
+ raw, _ := io.ReadAll(resp.Body) // 读到 EOF 保证 TCP 连接复用 Keep-Alive
|
|
|
|
|
+ resp.Body.Close()
|
|
|
|
|
+ status := int64(resp.StatusCode)
|
|
|
|
|
+ if status >= http.StatusBadRequest {
|
|
|
|
|
+ return nil, status, fmt.Errorf("挂载目录 %s 失败,外挂程序返回 HTTP %d: %s", dirLinuxPath, status, string(raw))
|
|
|
|
|
+ }
|
|
|
|
|
+ return map[string]interface{}{"status": status, "body": string(raw)}, status, nil
|
|
|
|
|
+ }); callErr != nil {
|
|
|
|
|
+ return callErr.Error()
|
|
|
}
|
|
}
|
|
|
- _, _ = io.Copy(io.Discard, resp.Body) // 读入"黑洞"保证 TCP 连接复用 Keep-Alive
|
|
|
|
|
- resp.Body.Close()
|
|
|
|
|
l.Infof("fuse mount ok: %s -> %s", dirLinuxPath, dirLinuxPath+"-cipher")
|
|
l.Infof("fuse mount ok: %s -> %s", dirLinuxPath, dirLinuxPath+"-cipher")
|
|
|
return ""
|
|
return ""
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// fuseUnmountOne 通知外挂程序卸载单个挂载点。成功返回空串,失败返回警告文案。
|
|
// fuseUnmountOne 通知外挂程序卸载单个挂载点。成功返回空串,失败返回警告文案。
|
|
|
// 卸载接口幂等:挂载点不在外挂注册表中也返回成功,所以重存/多卸都零成本。
|
|
// 卸载接口幂等:挂载点不在外挂注册表中也返回成功,所以重存/多卸都零成本。
|
|
|
-func (l *UpdateServiceLogic) fuseUnmountOne(ip string, port int64, mount, iden string) string {
|
|
|
|
|
|
|
+func (l *UpdateServiceLogic) fuseUnmountOne(ip string, port int64, mount string) string {
|
|
|
cleanMount := path.Clean(mount) // 同上,发给 Linux 必须保持 / 分隔符
|
|
cleanMount := path.Clean(mount) // 同上,发给 Linux 必须保持 / 分隔符
|
|
|
baseURL := fmt.Sprintf("http://%s:%d/api/fuseUnMount", ip, port) // %d:port 是 int64
|
|
baseURL := fmt.Sprintf("http://%s:%d/api/fuseUnMount", ip, port) // %d:port 是 int64
|
|
|
reqURL, uerr := url.Parse(baseURL)
|
|
reqURL, uerr := url.Parse(baseURL)
|
|
@@ -248,18 +254,25 @@ func (l *UpdateServiceLogic) fuseUnmountOne(ip string, port int64, mount, iden s
|
|
|
}
|
|
}
|
|
|
q := reqURL.Query()
|
|
q := reqURL.Query()
|
|
|
q.Set("mount", cleanMount) // q.Encode() 自动做 URL 编码(/ → %2F),外挂侧 Query().Get 自动解码还原
|
|
q.Set("mount", cleanMount) // q.Encode() 自动做 URL 编码(/ → %2F),外挂侧 Query().Get 自动解码还原
|
|
|
- reqURL.RawQuery = q.Encode()
|
|
|
|
|
- resp, err := doFuseRequest(fuseMountClient, iden, http.MethodGet, reqURL.String(), nil, "") // 复用挂载逻辑的 HTTP 客户端(同包包级变量)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return fmt.Sprintf("卸载目录 %s 失败(外挂程序未启动?): %v", cleanMount, err)
|
|
|
|
|
- }
|
|
|
|
|
- if resp.StatusCode >= http.StatusBadRequest {
|
|
|
|
|
- snippet, _ := io.ReadAll(io.LimitReader(resp.Body, 256))
|
|
|
|
|
- resp.Body.Close()
|
|
|
|
|
- return fmt.Sprintf("卸载目录 %s 失败,外挂程序返回 HTTP %d: %s", cleanMount, resp.StatusCode, string(snippet))
|
|
|
|
|
|
|
+ finalURL := reqURL.String()
|
|
|
|
|
+ // DoSimple 记录 type=2 接口日志,父 Span 指向本次入站请求(GET 请求用 url 描述请求原文)
|
|
|
|
|
+ if _, callErr := audit.DoSimple(l.svcCtx.SpanRecorder, l.ctx, "FuseUnMount", finalURL,
|
|
|
|
|
+ audit.BuildRemoteGetRequest(http.MethodGet, finalURL),
|
|
|
|
|
+ func() (map[string]interface{}, int64, error) {
|
|
|
|
|
+ resp, rerr := fuseMountClient.Get(finalURL)
|
|
|
|
|
+ if rerr != nil {
|
|
|
|
|
+ return nil, 0, fmt.Errorf("卸载目录 %s 失败(外挂程序未启动?): %w", cleanMount, rerr)
|
|
|
|
|
+ }
|
|
|
|
|
+ raw, _ := io.ReadAll(resp.Body) // 读到 EOF 保证 TCP 连接复用 Keep-Alive
|
|
|
|
|
+ resp.Body.Close()
|
|
|
|
|
+ status := int64(resp.StatusCode)
|
|
|
|
|
+ if status >= http.StatusBadRequest {
|
|
|
|
|
+ return nil, status, fmt.Errorf("卸载目录 %s 失败,外挂程序返回 HTTP %d: %s", cleanMount, status, string(raw))
|
|
|
|
|
+ }
|
|
|
|
|
+ return map[string]interface{}{"status": status, "body": string(raw)}, status, nil
|
|
|
|
|
+ }); callErr != nil {
|
|
|
|
|
+ return callErr.Error()
|
|
|
}
|
|
}
|
|
|
- _, _ = io.Copy(io.Discard, resp.Body)
|
|
|
|
|
- resp.Body.Close()
|
|
|
|
|
l.Infof("fuse unmount ok: %s", cleanMount)
|
|
l.Infof("fuse unmount ok: %s", cleanMount)
|
|
|
return ""
|
|
return ""
|
|
|
}
|
|
}
|