Skip to content

Commit 176fd0f

Browse files
authored
feat: support file operations inside containers (#12160)
Refs #523
1 parent 341a55a commit 176fd0f

13 files changed

Lines changed: 2219 additions & 6 deletions

File tree

agent/app/api/v2/container.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package v2
22

33
import (
4+
"net/http"
5+
"net/url"
6+
"path"
47
"strconv"
58

69
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
@@ -53,6 +56,162 @@ func (b *BaseApi) LoadContainerUsers(c *gin.Context) {
5356
helper.SuccessWithData(c, containerService.LoadUsers(req))
5457
}
5558

59+
// @Tags Container
60+
// @Summary List container files
61+
// @Accept json
62+
// @Param request body dto.ContainerFileReq true "request"
63+
// @Success 200 {array} dto.ContainerFileInfo
64+
// @Security ApiKeyAuth
65+
// @Security Timestamp
66+
// @Router /containers/files/search [post]
67+
func (b *BaseApi) ListContainerFiles(c *gin.Context) {
68+
var req dto.ContainerFileReq
69+
if err := helper.CheckBindAndValidate(&req, c); err != nil {
70+
return
71+
}
72+
files, err := containerService.ListContainerFiles(req)
73+
if err != nil {
74+
helper.InternalServer(c, err)
75+
return
76+
}
77+
helper.SuccessWithData(c, files)
78+
}
79+
80+
// @Tags Container
81+
// @Summary Upload container file
82+
// @Accept multipart/form-data
83+
// @Param containerID formData string true "containerID"
84+
// @Param path formData string true "path"
85+
// @Param file formData file true "file"
86+
// @Success 200
87+
// @Security ApiKeyAuth
88+
// @Security Timestamp
89+
// @Router /containers/files/upload [post]
90+
// @x-panel-log {"bodyKeys":["containerID","path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"容器 [containerID] 上传文件到 [path]","formatEN":"Upload file to [path] in container [containerID]"}
91+
func (b *BaseApi) UploadContainerFile(c *gin.Context) {
92+
form, err := c.MultipartForm()
93+
if err != nil {
94+
helper.BadRequest(c, err)
95+
return
96+
}
97+
containerIDs := form.Value["containerID"]
98+
paths := form.Value["path"]
99+
uploadFiles := form.File["file"]
100+
if len(containerIDs) == 0 || len(paths) == 0 || len(uploadFiles) == 0 {
101+
helper.BadRequest(c, errors.New("invalid container file upload params"))
102+
return
103+
}
104+
req := dto.ContainerFileReq{
105+
ContainerID: containerIDs[0],
106+
Path: paths[0],
107+
}
108+
for _, uploadFile := range uploadFiles {
109+
file, err := uploadFile.Open()
110+
if err != nil {
111+
helper.InternalServer(c, err)
112+
return
113+
}
114+
err = containerService.UploadContainerFile(req, path.Base(uploadFile.Filename), uploadFile.Size, file)
115+
_ = file.Close()
116+
if err != nil {
117+
helper.InternalServer(c, err)
118+
return
119+
}
120+
}
121+
helper.Success(c)
122+
}
123+
124+
// @Tags Container
125+
// @Summary Get container file content
126+
// @Accept json
127+
// @Param request body dto.ContainerFileReq true "request"
128+
// @Success 200 {object} dto.ContainerFileContent
129+
// @Security ApiKeyAuth
130+
// @Security Timestamp
131+
// @Router /containers/files/content [post]
132+
func (b *BaseApi) GetContainerFileContent(c *gin.Context) {
133+
var req dto.ContainerFileReq
134+
if err := helper.CheckBindAndValidate(&req, c); err != nil {
135+
return
136+
}
137+
content, err := containerService.GetContainerFileContent(req)
138+
if err != nil {
139+
helper.InternalServer(c, err)
140+
return
141+
}
142+
helper.SuccessWithData(c, content)
143+
}
144+
145+
// @Tags Container
146+
// @Summary Get container file size
147+
// @Accept json
148+
// @Param request body dto.ContainerFileReq true "request"
149+
// @Success 200 {int} size
150+
// @Security ApiKeyAuth
151+
// @Security Timestamp
152+
// @Router /containers/files/size [post]
153+
func (b *BaseApi) GetContainerFileSize(c *gin.Context) {
154+
var req dto.ContainerFileReq
155+
if err := helper.CheckBindAndValidate(&req, c); err != nil {
156+
return
157+
}
158+
size, err := containerService.GetContainerFileSize(req)
159+
if err != nil {
160+
helper.InternalServer(c, err)
161+
return
162+
}
163+
helper.SuccessWithData(c, size)
164+
}
165+
166+
// @Tags Container
167+
// @Summary Delete container file
168+
// @Accept json
169+
// @Param request body dto.ContainerFileBatchDeleteReq true "request"
170+
// @Success 200
171+
// @Security ApiKeyAuth
172+
// @Security Timestamp
173+
// @Router /containers/files/del [post]
174+
// @x-panel-log {"bodyKeys":["containerID","paths"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除容器 [containerID] 文件 [paths]","formatEN":"Delete files [paths] in container [containerID]"}
175+
func (b *BaseApi) DeleteContainerFile(c *gin.Context) {
176+
var req dto.ContainerFileBatchDeleteReq
177+
if err := helper.CheckBindAndValidate(&req, c); err != nil {
178+
return
179+
}
180+
if err := containerService.DeleteContainerFile(req); err != nil {
181+
helper.InternalServer(c, err)
182+
return
183+
}
184+
helper.Success(c)
185+
}
186+
187+
// @Tags Container
188+
// @Summary Download container file
189+
// @Accept json
190+
// @Param request body dto.ContainerFileReq true "request"
191+
// @Security ApiKeyAuth
192+
// @Security Timestamp
193+
// @Router /containers/files/download [post]
194+
// @x-panel-log {"bodyKeys":["containerID","path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"下载容器 [containerID] 文件 [path]","formatEN":"Download file [path] from container [containerID]"}
195+
func (b *BaseApi) DownloadContainerFile(c *gin.Context) {
196+
var req dto.ContainerFileReq
197+
if err := c.ShouldBindJSON(&req); err != nil {
198+
helper.BadRequest(c, err)
199+
return
200+
}
201+
if req.ContainerID == "" || req.Path == "" {
202+
helper.BadRequest(c, errors.New("invalid container file download params"))
203+
return
204+
}
205+
reader, fileName, contentType, err := containerService.DownloadContainerFile(req)
206+
if err != nil {
207+
helper.InternalServer(c, err)
208+
return
209+
}
210+
defer reader.Close()
211+
c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(fileName))
212+
c.DataFromReader(http.StatusOK, -1, contentType, reader, nil)
213+
}
214+
56215
// @Tags Container
57216
// @Summary List containers
58217
// @Accept json

agent/app/dto/container.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,34 @@ type ContainerOptions struct {
4848
State string `json:"state"`
4949
}
5050

51+
type ContainerFileReq struct {
52+
ContainerID string `json:"containerID" validate:"required"`
53+
Path string `json:"path" validate:"required"`
54+
}
55+
56+
type ContainerFileBatchDeleteReq struct {
57+
ContainerID string `json:"containerID" validate:"required"`
58+
Paths []string `json:"paths" validate:"required,min=1,dive,required"`
59+
}
60+
61+
type ContainerFileInfo struct {
62+
Name string `json:"name"`
63+
Path string `json:"path"`
64+
IsDir bool `json:"isDir"`
65+
IsLink bool `json:"isLink"`
66+
LinkTo string `json:"linkTo"`
67+
Size int64 `json:"size"`
68+
Mode string `json:"mode"`
69+
ModTime string `json:"modTime"`
70+
}
71+
72+
type ContainerFileContent struct {
73+
Content string `json:"content"`
74+
Size int64 `json:"size"`
75+
Truncated bool `json:"truncated"`
76+
IsBinary bool `json:"isBinary"`
77+
}
78+
5179
type ContainerStatus struct {
5280
Created int `json:"created"`
5381
Running int `json:"running"`

0 commit comments

Comments
 (0)