package controller
import (
"archive/tar"
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"github.com/goodrain/rainbond/api/client/prometheus"
"github.com/goodrain/rainbond/api/proxy"
"github.com/goodrain/rainbond/api/util"
dbmodel "github.com/goodrain/rainbond/db/model"
"github.com/goodrain/rainbond/pkg/component/k8s"
"github.com/goodrain/rainbond/pkg/component/storage"
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
"github.com/go-chi/chi"
"github.com/goodrain/rainbond/api/handler"
api_model "github.com/goodrain/rainbond/api/model"
ctxutil "github.com/goodrain/rainbond/api/util/ctx"
httputil "github.com/goodrain/rainbond/util/http"
)
func (t *TenantStruct) AddServiceMonitors(w http.ResponseWriter, r *http.Request) {
var add api_model.AddServiceMonitorRequestStruct
ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &add, nil)
if !ok {
return
}
serviceID := r.Context().Value(ctxutil.ContextKey("service_id")).(string)
tenantID := r.Context().Value(ctxutil.ContextKey("tenant_id")).(string)
tsm, err := handler.GetServiceManager().AddServiceMonitor(tenantID, serviceID, add)
if err != nil {
httputil.ReturnBcodeError(r, w, err)
return
}
httputil.ReturnSuccess(r, w, tsm)
}
func (t *TenantStruct) DeleteServiceMonitors(w http.ResponseWriter, r *http.Request) {
serviceID := r.Context().Value(ctxutil.ContextKey("service_id")).(string)
tenantID := r.Context().Value(ctxutil.ContextKey("tenant_id")).(string)
name := chi.URLParam(r, "name")
tsm, err := handler.GetServiceManager().DeleteServiceMonitor(tenantID, serviceID, name)
if err != nil {
httputil.ReturnBcodeError(r, w, err)
return
}
httputil.ReturnSuccess(r, w, tsm)
}
func (t *TenantStruct) UpdateServiceMonitors(w http.ResponseWriter, r *http.Request) {
var update api_model.UpdateServiceMonitorRequestStruct
ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &update, nil)
if !ok {
return
}
name := chi.URLParam(r, "name")
serviceID := r.Context().Value(ctxutil.ContextKey("service_id")).(string)
tenantID := r.Context().Value(ctxutil.ContextKey("tenant_id")).(string)
tsm, err := handler.GetServiceManager().UpdateServiceMonitor(tenantID, serviceID, name, update)
if err != nil {
httputil.ReturnBcodeError(r, w, err)
return
}
httputil.ReturnSuccess(r, w, tsm)
}
func (t *TenantStruct) UploadPackage(w http.ResponseWriter, r *http.Request) {
SetPackageBuildCORSHeaders(w, r)
eventID := strings.TrimSpace(chi.URLParam(r, "eventID"))
switch r.Method {
case "POST":
if eventID == "" {
httputil.ReturnError(r, w, 400, "Failed to parse eventID.")
return
}
logrus.Debug("Start receive upload file: ", eventID)
reader, header, err := r.FormFile("packageTarFile")
if err != nil {
logrus.Errorf("Failed to parse upload file: %s", err.Error())
httputil.ReturnError(r, w, 501, "Failed to parse upload file.")
return
}
defer reader.Close()
dirName := fmt.Sprintf("/grdata/package_build/temp/events/%s", eventID)
storage.Default().StorageCli.MkdirAll(dirName)
fileName := fmt.Sprintf("%s/%s", dirName, header.Filename)
err = storage.Default().StorageCli.SaveFile(fileName, reader)
if err != nil {
httputil.ReturnError(r, w, 503, "Failed to save file: "+err.Error())
}
logrus.Debug("successful write file to: ", fileName)
httputil.ReturnSuccess(r, w, nil)
case "OPTIONS":
httputil.ReturnSuccess(r, w, nil)
}
}
func GetMonitorMetrics(w http.ResponseWriter, r *http.Request) {
target := r.FormValue("target")
var metricMetadatas []prometheus.Metadata
if target == "tenant" {
metricMetadatas = handler.GetMonitorHandle().GetTenantMonitorMetrics(r.FormValue("tenant"))
}
if target == "app" {
metricMetadatas = handler.GetMonitorHandle().GetAppMonitorMetrics(r.FormValue("tenant"), r.FormValue("app"))
}
if target == "component" {
metricMetadatas = handler.GetMonitorHandle().GetComponentMonitorMetrics(r.FormValue("tenant"), r.FormValue("component"))
}
httputil.ReturnSuccess(r, w, metricMetadatas)
}
var fileManage *FileManage
type FileManage struct {
socketproxy proxy.Proxy
clientset *clientset.Clientset
config *rest.Config
}
func GetFileManage() *FileManage {
if fileManage == nil {
fileManage = &FileManage{
socketproxy: proxy.CreateProxy("acp_node", "http", []string{"rbd-node:6100"}),
clientset: k8s.Default().Clientset,
config: k8s.Default().RestConfig,
}
}
return fileManage
}
func (f FileManage) UploadEvent(w http.ResponseWriter, r *http.Request) {
volumeName := w.Header().Get("volume_name")
userName := w.Header().Get("user_name")
tenantID := w.Header().Get("tenant_id")
serviceID := w.Header().Get("service_id")
fileName := w.Header().Get("file_name")
status := w.Header().Get("status")
msg := fmt.Sprintf("%v to upload file %v in storage %v", status, fileName, volumeName)
_, err := util.CreateEvent(dbmodel.TargetTypeService, "volume-file-upload", serviceID, tenantID, "", userName, status, msg, 1)
if err != nil {
logrus.Error("create event error: ", err)
httputil.ReturnError(r, w, 500, "操作失败")
}
httputil.ReturnSuccess(r, w, nil)
}
func (f FileManage) UploadFile(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
if origin == "" {
origin = "*"
}
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Custom-Header")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Max-Age", "3600")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
logrus.Debugf("开始处理文件上传请求,Method: %s, ContentType: %s, Origin: %s",
r.Method, r.Header.Get("Content-Type"), origin)
w.Header().Set("volume_name", r.FormValue("volume_name"))
w.Header().Set("user_name", r.FormValue("user_name"))
w.Header().Set("tenant_id", r.FormValue("tenant_id"))
w.Header().Set("service_id", r.FormValue("service_id"))
w.Header().Set("status", "failed")
destPath := r.FormValue("path")
podName := r.FormValue("pod_name")
namespace := r.FormValue("namespace")
containerName := r.FormValue("container_name")
logrus.Debugf("上传参数: destPath=%s, podName=%s, namespace=%s, containerName=%s",
destPath, podName, namespace, containerName)
if destPath == "" {
httputil.ReturnError(r, w, 400, "目标路径不能为空")
return
}
err := r.ParseMultipartForm(32 << 20)
if err != nil {
logrus.Errorf("解析multipart form失败: %v", err)
httputil.ReturnError(r, w, 400, fmt.Sprintf("解析上传数据失败: %v", err))
return
}
if r.MultipartForm == nil {
logrus.Error("MultipartForm为空")
httputil.ReturnError(r, w, 400, "未找到上传文件")
return
}
logrus.Debugf("Form字段: %+v", r.MultipartForm.Value)
logrus.Debugf("文件字段: %+v", r.MultipartForm.File)
var files []*multipart.FileHeader
if formFiles := r.MultipartForm.File["files"]; len(formFiles) > 0 {
files = formFiles
} else if formFiles := r.MultipartForm.File["file"]; len(formFiles) > 0 {
files = formFiles
}
if len(files) == 0 {
logrus.Error("未找到上传文件")
httputil.ReturnError(r, w, 400, "未找到上传文件")
return
}
tempDir, err := os.MkdirTemp("", "upload-*")
if err != nil {
logrus.Errorf("创建临时目录失败: %v", err)
httputil.ReturnError(r, w, 500, fmt.Sprintf("创建临时目录失败: %v", err))
return
}
defer os.RemoveAll(tempDir)
for _, fileHeader := range files {
logrus.Debugf("处理文件: %s", fileHeader.Filename)
var relativePath string
contentDisposition := fileHeader.Header.Get("Content-Disposition")
filenameRegex := regexp.MustCompile(`filename="([^"]+)"`)
matches := filenameRegex.FindStringSubmatch(contentDisposition)
if len(matches) > 1 {
relativePath = matches[1]
logrus.Debugf("使用 Content-Disposition 中的 filename: %s", relativePath)
} else if webkitPath := fileHeader.Header.Get("webkitRelativePath"); webkitPath != "" {
relativePath = webkitPath
logrus.Debugf("使用 webkitRelativePath: %s", relativePath)
} else {
relativePath = fileHeader.Filename
logrus.Debugf("使用文件名作为路径: %s", relativePath)
}
file, err := fileHeader.Open()
if err != nil {
logrus.Errorf("打开上传文件失败: %v", err)
httputil.ReturnError(r, w, 500, fmt.Sprintf("打开上传文件失败: %v", err))
return
}
defer file.Close()
tempPath := filepath.Join(tempDir, relativePath)
logrus.Debugf("临时文件路径: %s", tempPath)
if err := os.MkdirAll(filepath.Dir(tempPath), 0755); err != nil {
logrus.Errorf("创建目录失败: %v", err)
httputil.ReturnError(r, w, 500, fmt.Sprintf("创建目录失败: %v", err))
return
}
dst, err := os.Create(tempPath)
if err != nil {
logrus.Errorf("创建文件失败: %v", err)
httputil.ReturnError(r, w, 500, fmt.Sprintf("创建文件失败: %v", err))
return
}
defer dst.Close()
if _, err = io.Copy(dst, file); err != nil {
logrus.Errorf("保存文件失败: %v", err)
httputil.ReturnError(r, w, 500, fmt.Sprintf("保存文件失败: %v", err))
return
}
logrus.Debugf("成功保存文件: %s", tempPath)
}
logrus.Debugf("开始上传文件到容器,源目录: %s,目标路径: %s", tempDir, destPath)
firstFile := files[0]
if webkitPath := firstFile.Header.Get("webkitRelativePath"); webkitPath != "" {
err = f.AppFileUpload(containerName, podName, tempDir, destPath, namespace)
} else if len(files) == 1 {
uploadPath := filepath.Join(tempDir, firstFile.Filename)
err = f.AppFileUpload(containerName, podName, uploadPath, destPath, namespace)
} else {
err = f.AppFileUpload(containerName, podName, tempDir, destPath, namespace)
}
if err != nil {
logrus.Errorf("上传到容器失败: %v", err)
httputil.ReturnError(r, w, 500, fmt.Sprintf("上传失败: %v", err))
return
}
logrus.Debug("文件上传成功")
w.Header().Set("status", "success")
httputil.ReturnSuccess(r, w, nil)
}
func (f FileManage) DownloadFile(w http.ResponseWriter, r *http.Request) {
logrus.Debugf("接收到文件下载请求: Method=%s, ContentType=%s", r.Method, r.Header.Get("Content-Type"))
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("volume_name", r.FormValue("volume_name"))
w.Header().Set("user_name", r.FormValue("user_name"))
w.Header().Set("tenant_id", r.FormValue("tenant_id"))
w.Header().Set("service_id", r.FormValue("service_id"))
w.Header().Set("status", "failed")
podName := r.FormValue("pod_name")
p := r.FormValue("path")
namespace := r.FormValue("namespace")
fileName := strings.TrimSpace(chi.URLParam(r, "fileName"))
containerName := r.FormValue("container_name")
logrus.Debugf("下载文件参数: podName=%s, path=%s, namespace=%s, fileName=%s, containerName=%s",
podName, p, namespace, fileName, containerName)
if fileName == "" {
logrus.Error("文件名为空")
httputil.ReturnError(r, w, 400, "文件名不能为空")
return
}
filePath := path.Join(p, fileName)
logrus.Debugf("完整文件路径: %s", filePath)
err := f.AppFileDownload(containerName, podName, filePath, namespace)
if err != nil {
logrus.Errorf("从Pod下载文件失败: %v", err)
httputil.ReturnError(r, w, 500, fmt.Sprintf("下载文件失败: %v", err))
return
}
defer func() {
if err := os.Remove(path.Join("./", fileName)); err != nil {
logrus.Errorf("删除临时文件失败: %v", err)
}
}()
w.Header().Set("status", "success")
w.Header().Set("Content-Disposition", "attachment;filename="+fileName)
logrus.Debugf("开始传输文件: %s", fileName)
http.ServeFile(w, r, fileName)
}
func (f FileManage) AppFileDownload(containerName, podName, filePath, namespace string) error {
checkCmd := []string{"test", "-e", filePath}
req := f.clientset.CoreV1().RESTClient().Get().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: checkCmd,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(f.config, "POST", req.URL())
if err != nil {
return fmt.Errorf("failed to create executor: %v", err)
}
var checkStdout, checkStderr bytes.Buffer
err = exec.Stream(remotecommand.StreamOptions{
Stdout: &checkStdout,
Stderr: &checkStderr,
})
if err != nil {
return fmt.Errorf("file not found: %s", filePath)
}
isDirCmd := []string{"test", "-d", filePath}
req = f.clientset.CoreV1().RESTClient().Get().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: isDirCmd,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err = remotecommand.NewSPDYExecutor(f.config, "POST", req.URL())
if err != nil {
return fmt.Errorf("failed to create executor: %v", err)
}
var isDirStdout, isDirStderr bytes.Buffer
err = exec.Stream(remotecommand.StreamOptions{
Stdout: &isDirStdout,
Stderr: &isDirStderr,
})
isDirectory := (err == nil)
if !isDirectory {
return f.downloadFileUsingCat(containerName, podName, filePath, namespace)
}
err = f.downloadUsingTar(containerName, podName, filePath, namespace)
if err != nil && strings.Contains(err.Error(), "tar") {
logrus.Warnf("tar command not available, falling back to simple file copy: %v", err)
return f.downloadFileUsingCat(containerName, podName, filePath, namespace)
}
return err
}
func (f FileManage) downloadFileUsingCat(containerName, podName, filePath, namespace string) error {
req := f.clientset.CoreV1().RESTClient().Get().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: []string{"cat", filePath},
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(f.config, "POST", req.URL())
if err != nil {
return fmt.Errorf("failed to create executor: %v", err)
}
fileName := path.Base(filePath)
destPath := path.Join("./", fileName)
outFile, err := os.Create(destPath)
if err != nil {
return fmt.Errorf("failed to create local file: %v", err)
}
defer outFile.Close()
var stderr bytes.Buffer
err = exec.Stream(remotecommand.StreamOptions{
Stdout: outFile,
Stderr: &stderr,
Tty: false,
})
if err != nil {
return fmt.Errorf("failed to download file using cat: %v, stderr: %s", err, stderr.String())
}
logrus.Debugf("Successfully downloaded file %s using cat command", fileName)
return nil
}
func (f FileManage) downloadUsingTar(containerName, podName, filePath, namespace string) error {
reader, outStream := io.Pipe()
req := f.clientset.CoreV1().RESTClient().Get().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: []string{"tar", "cf", "-", filePath},
Stdin: true,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(f.config, "POST", req.URL())
if err != nil {
return fmt.Errorf("failed to create executor: %v", err)
}
go func() {
defer outStream.Close()
err = exec.Stream(remotecommand.StreamOptions{
Stdin: os.Stdin,
Stdout: outStream,
Stderr: os.Stderr,
Tty: false,
})
if err != nil {
logrus.Errorf("stream error: %v", err)
}
}()
prefix := getPrefix(filePath)
prefix = path.Clean(prefix)
destPath := path.Join("./", path.Base(prefix))
err = unTarAll(reader, destPath, prefix)
if err != nil {
return fmt.Errorf("failed to untar: %v", err)
}
return nil
}
func (f FileManage) AppFileUpload(containerName, podName, srcPath, destPath, namespace string) error {
logrus.Debugf("开始上传目录/文件: 源路径=%s, 目标路径=%s", srcPath, destPath)
srcInfo, err := os.Stat(srcPath)
if err != nil {
return fmt.Errorf("源路径检查失败: %v", err)
}
if !srcInfo.IsDir() {
return f.uploadFileUsingTee(containerName, podName, srcPath, destPath, namespace)
}
err = f.uploadUsingTar(containerName, podName, srcPath, destPath, namespace)
if err != nil && strings.Contains(err.Error(), "tar") {
logrus.Warnf("tar command not available, falling back to recursive file copy: %v", err)
return f.uploadDirectoryUsingTee(containerName, podName, srcPath, destPath, namespace)
}
return err
}
func (f FileManage) uploadUsingTar(containerName, podName, srcPath, destPath, namespace string) error {
srcInfo, err := os.Stat(srcPath)
if err != nil {
return fmt.Errorf("源路径检查失败: %v", err)
}
reader, writer := io.Pipe()
go func() {
defer writer.Close()
if srcInfo.IsDir() {
logrus.Debugf("正在打包目录: %s", srcPath)
err = cpMakeTar(srcPath, destPath, writer)
} else {
logrus.Debugf("正在打包文件: %s", srcPath)
err = cpMakeTar(filepath.Dir(srcPath), destPath, writer)
}
if err != nil {
logrus.Errorf("tar打包失败: %v", err)
writer.CloseWithError(err)
return
}
}()
var cmdArr []string
cmdArr = []string{"tar", "-xmf", "-"}
if len(destPath) > 0 {
mkdirCmd := []string{"mkdir", "-p", destPath}
mkdirReq := f.clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: mkdirCmd,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
mkdirExec, err := remotecommand.NewSPDYExecutor(f.config, "POST", mkdirReq.URL())
if err != nil {
return fmt.Errorf("创建目标目录执行器失败: %v", err)
}
if err := mkdirExec.Stream(remotecommand.StreamOptions{
Stdout: os.Stdout,
Stderr: os.Stderr,
}); err != nil {
return fmt.Errorf("在容器中创建目录失败: %v", err)
}
cmdArr = append(cmdArr, "-C", destPath)
}
req := f.clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: cmdArr,
Stdin: true,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(f.config, "POST", req.URL())
if err != nil {
return fmt.Errorf("创建解压执行器失败: %v", err)
}
logrus.Debug("开始在容器中解压文件")
err = exec.Stream(remotecommand.StreamOptions{
Stdin: reader,
Stdout: os.Stdout,
Stderr: os.Stderr,
Tty: false,
})
if err != nil {
return fmt.Errorf("在容器中解压失败: %v", err)
}
logrus.Debug("文件/目录上传完成")
return nil
}
func (f FileManage) uploadDirectoryUsingTee(containerName, podName, srcPath, destPath, namespace string) error {
logrus.Debugf("使用递归方式上传目录: %s -> %s", srcPath, destPath)
return filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(srcPath, path)
if err != nil {
return fmt.Errorf("获取相对路径失败: %v", err)
}
var targetPath string
if relPath == "." {
targetPath = destPath
} else {
targetPath = filepath.Join(destPath, relPath)
}
if info.IsDir() {
if relPath != "." {
mkdirCmd := []string{"mkdir", "-p", targetPath}
req := f.clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: mkdirCmd,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(f.config, "POST", req.URL())
if err != nil {
return fmt.Errorf("创建目录执行器失败: %v", err)
}
var stderr bytes.Buffer
if err := exec.Stream(remotecommand.StreamOptions{
Stdout: &bytes.Buffer{},
Stderr: &stderr,
}); err != nil {
logrus.Warnf("创建目录警告: %v, stderr: %s", err, stderr.String())
}
}
return nil
}
logrus.Debugf("上传文件: %s -> %s", path, targetPath)
srcFile, err := os.Open(path)
if err != nil {
return fmt.Errorf("打开源文件失败: %v", err)
}
defer srcFile.Close()
teeCmd := []string{"tee", targetPath}
req := f.clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: teeCmd,
Stdin: true,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(f.config, "POST", req.URL())
if err != nil {
return fmt.Errorf("创建 tee 执行器失败: %v", err)
}
var stderr bytes.Buffer
err = exec.Stream(remotecommand.StreamOptions{
Stdin: srcFile,
Stdout: &bytes.Buffer{},
Stderr: &stderr,
Tty: false,
})
if err != nil {
return fmt.Errorf("上传文件失败: %v, stderr: %s", err, stderr.String())
}
return nil
})
}
func (f FileManage) checkTarAvailable(containerName, podName, namespace string) bool {
checkCmd := []string{"tar", "--version"}
req := f.clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: checkCmd,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(f.config, "POST", req.URL())
if err != nil {
return false
}
var stdout, stderr bytes.Buffer
err = exec.Stream(remotecommand.StreamOptions{
Stdout: &stdout,
Stderr: &stderr,
})
return err == nil
}
func (f FileManage) uploadFileUsingTee(containerName, podName, srcPath, destPath, namespace string) error {
srcFile, err := os.Open(srcPath)
if err != nil {
return fmt.Errorf("failed to open source file: %v", err)
}
defer srcFile.Close()
fileName := filepath.Base(srcPath)
var fullPath string
if strings.HasSuffix(destPath, "/") || destPath == "" {
fullPath = path.Join(destPath, fileName)
} else {
isDirCmd := []string{"test", "-d", destPath}
isDirReq := f.clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: isDirCmd,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
isDirExec, err := remotecommand.NewSPDYExecutor(f.config, "POST", isDirReq.URL())
if err != nil {
return fmt.Errorf("failed to create directory check executor: %v", err)
}
var isDirStdout, isDirStderr bytes.Buffer
err = isDirExec.Stream(remotecommand.StreamOptions{
Stdout: &isDirStdout,
Stderr: &isDirStderr,
})
if err == nil {
fullPath = path.Join(destPath, fileName)
} else {
fullPath = destPath
}
}
dirPath := path.Dir(fullPath)
if dirPath != "." && dirPath != "/" {
mkdirCmd := []string{"mkdir", "-p", dirPath}
req := f.clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: mkdirCmd,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(f.config, "POST", req.URL())
if err != nil {
return fmt.Errorf("failed to create mkdir executor: %v", err)
}
var stderr bytes.Buffer
if err := exec.Stream(remotecommand.StreamOptions{
Stdout: &bytes.Buffer{},
Stderr: &stderr,
}); err != nil {
logrus.Warnf("mkdir warning: %v, stderr: %s", err, stderr.String())
}
}
teeCmd := []string{"tee", fullPath}
req := f.clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: teeCmd,
Stdin: true,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(f.config, "POST", req.URL())
if err != nil {
return fmt.Errorf("failed to create tee executor: %v", err)
}
var stderr bytes.Buffer
err = exec.Stream(remotecommand.StreamOptions{
Stdin: srcFile,
Stdout: &bytes.Buffer{},
Stderr: &stderr,
Tty: false,
})
if err != nil {
return fmt.Errorf("failed to upload file using tee: %v, stderr: %s", err, stderr.String())
}
logrus.Debugf("Successfully uploaded file %s using tee command", fileName)
return nil
}
func cpMakeTar(srcPath string, destPath string, out io.Writer) error {
tw := tar.NewWriter(out)
defer tw.Close()
srcInfo, err := os.Stat(srcPath)
if err != nil {
return fmt.Errorf("failed to get info for %s: %v", srcPath, err)
}
basePath := srcPath
if srcInfo.IsDir() {
basePath = srcPath
} else {
basePath = filepath.Dir(srcPath)
}
return filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(basePath, path)
if err != nil {
return err
}
if relPath == "." && srcInfo.IsDir() {
return nil
}
hdr, err := tar.FileInfoHeader(info, "")
if err != nil {
return fmt.Errorf("failed to create header for %s: %v", path, err)
}
if srcInfo.IsDir() {
hdr.Name = relPath
} else {
hdr.Name = filepath.Base(srcPath)
}
if info.IsDir() {
hdr.Name += "/"
}
logrus.Debugf("Adding to tar: %s", hdr.Name)
if err := tw.WriteHeader(hdr); err != nil {
return fmt.Errorf("failed to write header for %s: %v", path, err)
}
if !info.IsDir() {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
if _, err := io.Copy(tw, file); err != nil {
return fmt.Errorf("failed to write file %s: %v", path, err)
}
}
return nil
})
}
func unTarAll(reader io.Reader, destDir, prefix string) error {
tarReader := tar.NewReader(reader)
for {
header, err := tarReader.Next()
if err != nil {
if err != io.EOF {
return err
}
break
}
if !strings.HasPrefix(header.Name, prefix) {
return fmt.Errorf("tar contents corrupted")
}
mode := header.FileInfo().Mode()
destFileName := filepath.Join(destDir, header.Name[len(prefix):])
baseName := filepath.Dir(destFileName)
if err := os.MkdirAll(baseName, 0755); err != nil {
return err
}
if header.FileInfo().IsDir() {
if err := os.MkdirAll(destFileName, 0755); err != nil {
return err
}
continue
}
evaledPath, err := filepath.EvalSymlinks(baseName)
if err != nil {
return err
}
if mode&os.ModeSymlink != 0 {
linkname := header.Linkname
if !filepath.IsAbs(linkname) {
_ = filepath.Join(evaledPath, linkname)
}
if err := os.Symlink(linkname, destFileName); err != nil {
return err
}
} else {
outFile, err := os.Create(destFileName)
if err != nil {
return err
}
defer outFile.Close()
if _, err := io.Copy(outFile, tarReader); err != nil {
return err
}
if err := outFile.Close(); err != nil {
return err
}
}
}
return nil
}
func getPrefix(file string) string {
return strings.TrimLeft(file, "/")
}
func (f FileManage) CreateDirectory(w http.ResponseWriter, r *http.Request) {
logrus.Debugf("接收到创建目录请求: Method=%s, ContentType=%s", r.Method, r.Header.Get("Content-Type"))
podName := r.FormValue("pod_name")
namespace := r.FormValue("namespace")
containerName := r.FormValue("container_name")
dirPath := r.FormValue("path")
logrus.Debugf("创建目录参数: podName=%s, namespace=%s, containerName=%s, dirPath=%s",
podName, namespace, containerName, dirPath)
if dirPath == "" {
logrus.Error("目录路径为空")
httputil.ReturnError(r, w, 400, "目录路径不能为空")
return
}
req := f.clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: []string{"mkdir", "-p", dirPath},
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(f.config, "POST", req.URL())
if err != nil {
logrus.Errorf("创建目录执行器失败: %v", err)
httputil.ReturnError(r, w, 500, fmt.Sprintf("创建目录失败: %v", err))
return
}
var stdout, stderr bytes.Buffer
err = exec.Stream(remotecommand.StreamOptions{
Stdin: nil,
Stdout: &stdout,
Stderr: &stderr,
Tty: false,
})
if stdout.Len() > 0 {
logrus.Debugf("命令输出: %s", stdout.String())
}
if stderr.Len() > 0 {
logrus.Debugf("错误输出: %s", stderr.String())
}
if err != nil {
logrus.Errorf("在容器中创建目录失败: %v", err)
httputil.ReturnError(r, w, 500, fmt.Sprintf("创建目录失败: %v", err))
return
}
logrus.Debugf("目录创建成功: %s", dirPath)
httputil.ReturnSuccess(r, w, nil)
}