Compare commits
10 Commits
4e9d9b9cb0
...
b45ddb291f
| Author | SHA1 | Date |
|---|---|---|
|
|
b45ddb291f | |
|
|
b1ab2df214 | |
|
|
e4ddd0b445 | |
|
|
a36de499c3 | |
|
|
b50bd2c776 | |
|
|
5e02216d65 | |
|
|
66e9a60bf4 | |
|
|
9e3f47b149 | |
|
|
c40e273538 | |
|
|
f22294056e |
|
|
@ -0,0 +1,192 @@
|
||||||
|
# API 文档
|
||||||
|
|
||||||
|
## 总体设计
|
||||||
|
|
||||||
|
### 基本HTTP请求
|
||||||
|
|
||||||
|
本系统后端API设计遵循RESTful规范,对于一般数据类型的增删改查使用同一URL下的POST、DELETE、PUT和GET方法进行访问。
|
||||||
|
对于POST和PUT方法,待传输数据放在请求体中,以Json格式进行传输。DELETE和GET方法使用URL参数传递必要数据。POST为上传新数据,使用POST时数据中ID字段将被忽略。
|
||||||
|
|
||||||
|
后端返回数据也全部为Json格式,使用HTTP状态码标识请求状态。当状态码为200时可以认为该请求成功达到了预期的结果。若请求失败,则状态码不为200,并且返回数据中包含`msg`
|
||||||
|
字段对发生的错误进行了简要说明。
|
||||||
|
|
||||||
|
一次失败的请求返回数据如下所示,其状态码为401(StatusUnauthorized)。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": false,
|
||||||
|
"msg": "username error"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
一般数据类型包含`ID`字段,类型为整数。DELETE方法可用此字段表示将要删除的对象。删除一个镜像的请求如下命令所示。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X DELETE https://ms.educg.net/admin/image?ID=1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 资源查询请求
|
||||||
|
|
||||||
|
对单个资源查询的方式与删除类似,可使用该数据结构中的某些字段对数据进行查询,返回结果为Json格式。
|
||||||
|
|
||||||
|
同时对该类型的多个资源进行查询时,URL一般为该资源类型的复数形式,如镜像的查询URL为`image`
|
||||||
|
,那么同时获得多个镜像的查询的URL为`images`。特殊情况另行说明。
|
||||||
|
对多个数据进行查询时,URL中除了必要的过滤条件外,还可包含分页参数`page`和`pageSize`,均为整数类型。返回结果中`data`
|
||||||
|
字段为查询结果数据,`page`、`pageCount`和`pageSize`用于控制分页。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"page": 1,
|
||||||
|
"pageCount": 100,
|
||||||
|
"pageSize": 2,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"ID": 2,
|
||||||
|
"Other fields": "Other Values"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据结构
|
||||||
|
数据结构使用Golang语言格式进行定义,其中`gorm.Model`为公用字段,定义如下:
|
||||||
|
```go
|
||||||
|
type Model struct {
|
||||||
|
ID uint `gorm:"primarykey"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt DeletedAt `gorm:"index"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
数据结构中包含的Comment字段为该数据的简单描述,Doc字段为该数据的详细文档,文档应支持Markdown格式保存和渲染。
|
||||||
|
在所有创建对象的请求(POST)中,ID字段将被忽略,创建成功后自动分配ID。
|
||||||
|
|
||||||
|
## 镜像管理
|
||||||
|
|
||||||
|
数据结构:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type ImageModel struct {
|
||||||
|
gorm.Model
|
||||||
|
Name string
|
||||||
|
Image string
|
||||||
|
Comment string
|
||||||
|
Command string
|
||||||
|
Env string
|
||||||
|
Port string
|
||||||
|
UserName string
|
||||||
|
IsPublished bool
|
||||||
|
Doc string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
CURD:
|
||||||
|
+ POST:/image
|
||||||
|
+ PUT: /image
|
||||||
|
+ DELETE: /image?ID=1
|
||||||
|
+ GET /images?UserName=cg_wmj # 查询某用户的镜像
|
||||||
|
+ GET /images?IsPublish=1 # 查询所有公开镜像
|
||||||
|
|
||||||
|
## 数据集管理
|
||||||
|
|
||||||
|
训练数据,数据结构:
|
||||||
|
```go
|
||||||
|
type DataModel struct {
|
||||||
|
gorm.Model
|
||||||
|
Type string
|
||||||
|
Name string
|
||||||
|
Comment string
|
||||||
|
Doc string
|
||||||
|
UserName string
|
||||||
|
AccessInfo string
|
||||||
|
IsPublished bool
|
||||||
|
}
|
||||||
|
```
|
||||||
|
其中`Type`字段为数据集的类型,表示该数据集的访问方式,目前仅支持`Type=="dir"`访问目录格式数据,后续可能支持mysql等数据库。
|
||||||
|
本系统中,对本地文件的访问使用Webdav协议。
|
||||||
|
`AccessInfo`字段为该数据集的访问方式,在`Type=="dir"`时为该目录的路径。
|
||||||
|
例如某数据集对象中,`AccessInfo=='/some/data/dir/'`,同时文件服务器地址为`https://ms.educg.net/files/ `,那么通过WebDav协议访问`https://ms.educg.net/files/some/data/dir/ `即可访问该目录。
|
||||||
|
|
||||||
|
|
||||||
|
CURD:
|
||||||
|
+ POST:/data
|
||||||
|
+ PUT: /data
|
||||||
|
+ DELETE: /data?ID=1
|
||||||
|
+ GET /data?UserName=cg_wmj
|
||||||
|
+ GET /data?IsPublish=1
|
||||||
|
|
||||||
|
## 项目管理
|
||||||
|
|
||||||
|
数据结构
|
||||||
|
```go
|
||||||
|
type ProjectModel struct {
|
||||||
|
gorm.Model
|
||||||
|
ProjectName string
|
||||||
|
UserName string
|
||||||
|
GroupName string
|
||||||
|
Type string
|
||||||
|
Comment string
|
||||||
|
Doc string
|
||||||
|
IsPublic bool
|
||||||
|
|
||||||
|
Image ImageModel
|
||||||
|
TrainData []*ProjectTrainDataModel `gorm:"foreignKey:ProjectID"`
|
||||||
|
|
||||||
|
|
||||||
|
UserPath string // 用户目录挂载位置
|
||||||
|
CodePath string // 代码目录挂载位置
|
||||||
|
ModelPath string // 模型目录挂载位置
|
||||||
|
|
||||||
|
Running bool `gorm:"-:all"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type ProjectTrainDataModel struct {
|
||||||
|
BaseModel[ProjectTrainDataModel]
|
||||||
|
Project *ProjectModel
|
||||||
|
Data *DataModel
|
||||||
|
MountPath string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
其中`Type`字段为项目的类型,可选值为`jupyter`、`jupyterlab`、`jupyternotebook`、`novnc`、`vscode`、`http`。
|
||||||
|
|
||||||
|
在创建或更新项目时,`TrainData`字段应该为DataModel结构的列表,其中每一个DataModel仅有`ID`即可。
|
||||||
|
|
||||||
|
CURD:
|
||||||
|
+ POST:/project
|
||||||
|
+ PUT: /project
|
||||||
|
+ DELETE: /project?ID=1
|
||||||
|
+ GET /projects?UserName=cg_wmj
|
||||||
|
+ GET /projects?IsPublish=1
|
||||||
|
|
||||||
|
## 运行项目 (尚未实现)
|
||||||
|
|
||||||
|
数据结构
|
||||||
|
```go
|
||||||
|
type RunningProject struct {
|
||||||
|
ID int
|
||||||
|
Project *ProjectModel
|
||||||
|
StartTime *time.Time
|
||||||
|
UserName string
|
||||||
|
Nodes int
|
||||||
|
Cpus int
|
||||||
|
Memory int
|
||||||
|
Gpus int
|
||||||
|
GpuMemory int
|
||||||
|
URL string
|
||||||
|
ControlURL string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
运行一个项目,前端发送POST请求时URL可留空,若该项目成功启动,则POST的返回内容中将包含正确的URL。
|
||||||
|
通过此URL即可访问项目服务。ControlURL为容器服务客户端连接地址,可通过本地客户端提供更多功能。
|
||||||
|
|
||||||
|
CURD:
|
||||||
|
+ POST:/running_project
|
||||||
|
+ DELETE: /running_project?ID=1
|
||||||
|
+ GET /running_projects?UserName=cg_wmj
|
||||||
1
Makefile
1
Makefile
|
|
@ -23,6 +23,7 @@ restart: docker
|
||||||
|
|
||||||
install-dev: push
|
install-dev: push
|
||||||
scp -i C:${HOMEPATH}/.ssh/id_rsa install-dev.yaml root@192.168.131.195:/root/install-backend-dev.yaml
|
scp -i C:${HOMEPATH}/.ssh/id_rsa install-dev.yaml root@192.168.131.195:/root/install-backend-dev.yaml
|
||||||
|
ssh -i C:${HOMEPATH}/.ssh/id_rsa root@192.168.131.195 kubectl delete -f /root/install-backend-dev.yaml
|
||||||
ssh -i C:${HOMEPATH}/.ssh/id_rsa root@192.168.131.195 kubectl apply -f /root/install-backend-dev.yaml
|
ssh -i C:${HOMEPATH}/.ssh/id_rsa root@192.168.131.195 kubectl apply -f /root/install-backend-dev.yaml
|
||||||
|
|
||||||
push: docker
|
push: docker
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,10 @@ EOF
|
||||||
|
|
||||||
kubectl get secret dev-secret -o yaml
|
kubectl get secret dev-secret -o yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 持续查看Log
|
||||||
|
|
||||||
|
```bash
|
||||||
|
while true; do bash -c "kubectl get pods | grep k8s-manager-deployment | awk '{print \$1}' | xargs kubectl logs -f"; sleep 1; done
|
||||||
|
```
|
||||||
|
|
@ -22,7 +22,7 @@ spec:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
env:
|
env:
|
||||||
- name: GIN_MODE
|
- name: GIN_MODE
|
||||||
value: release
|
value: debug
|
||||||
- name: URL_PREFIX
|
- name: URL_PREFIX
|
||||||
value: admin
|
value: admin
|
||||||
- name: DB_HOST
|
- name: DB_HOST
|
||||||
|
|
@ -70,6 +70,7 @@ metadata:
|
||||||
name: manager-ingress
|
name: manager-ingress
|
||||||
annotations:
|
annotations:
|
||||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||||
|
nginx.ingress.kubernetes.io/proxy-body-size: 10g
|
||||||
spec:
|
spec:
|
||||||
ingressClassName: nginx
|
ingressClassName: nginx
|
||||||
rules:
|
rules:
|
||||||
|
|
|
||||||
10
main.go
10
main.go
|
|
@ -73,6 +73,16 @@ func main() {
|
||||||
|
|
||||||
r.POST("/fork", controller.ForkApp)
|
r.POST("/fork", controller.ForkApp)
|
||||||
|
|
||||||
|
// controller.AddWebdavHandler(r)
|
||||||
|
|
||||||
|
r.POST("/image", controller.AddImage)
|
||||||
|
r.PUT("/image", controller.UpdateImage)
|
||||||
|
r.DELETE("/image", controller.DeleteImage)
|
||||||
|
r.GET("/images", controller.GetImages)
|
||||||
|
|
||||||
|
controller.AddDataController(r)
|
||||||
|
controller.AddProjectController(r)
|
||||||
|
|
||||||
err := r.Run(":8000")
|
err := r.Run(":8000")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"k8s-manager/src/model"
|
||||||
|
"k8s-manager/src/utils"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaseController[T model.ModalType, PT model.ModalInterface[T]] struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (BaseController[T, PT]) Add(t *T) (utils.OKType, error) {
|
||||||
|
PT(t).Create()
|
||||||
|
return utils.OK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (BaseController[T, PT]) Delete(ID int) (utils.OKType, error) {
|
||||||
|
data := PT(new(T))
|
||||||
|
data.SetID(uint(ID))
|
||||||
|
data.Delete()
|
||||||
|
return utils.OK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (BaseController[T, PT]) Update(username string, t *T) (utils.OKType, error) {
|
||||||
|
data := PT(t)
|
||||||
|
oldData := PT(new(T))
|
||||||
|
oldData.SetID(data.GetID())
|
||||||
|
oldData.Fill()
|
||||||
|
if oldData.GetUserName() != username {
|
||||||
|
return nil, errors.New("wrong username")
|
||||||
|
}
|
||||||
|
data.Save()
|
||||||
|
return utils.OK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (BaseController[T, PT]) Gets(c *gin.Context) {
|
||||||
|
username := c.MustGet("username").(string)
|
||||||
|
var ans []any
|
||||||
|
query := map[string]interface{}{}
|
||||||
|
if c.Query("UserName") != "" {
|
||||||
|
if username != c.Query("UserName") {
|
||||||
|
utils.GinError(c, http.StatusUnauthorized, "username error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
query["UserName"] = username
|
||||||
|
} else if c.Query("IsPublic") == "1" {
|
||||||
|
query["IsPublic"] = true
|
||||||
|
} else {
|
||||||
|
utils.GinError(c, http.StatusBadRequest, "param error.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ans = PT(new(T)).Gets(query)
|
||||||
|
c.JSON(http.StatusOK, gin.H{"data": ans})
|
||||||
|
}
|
||||||
|
|
||||||
|
//func AddCRUDRoute[T model.ModalType](r *gin.Engine, path string) {
|
||||||
|
// t := BaseController[T, *T]{}
|
||||||
|
// typeName := reflect.TypeOf((*T)(nil)).Name()
|
||||||
|
// utils.RouteWrapper(r, "POST", path, t.Add, []string{typeName})
|
||||||
|
// utils.RouteWrapper(r, "PUT", path, t.Update, []string{"username", typeName})
|
||||||
|
// utils.RouteWrapper(r, "DELETE", path, t.Delete, []string{"ID"})
|
||||||
|
// r.Handle("GET", path, t.Gets)
|
||||||
|
//}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"k8s-manager/src/model"
|
||||||
|
"k8s-manager/src/utils"
|
||||||
|
"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddData(username string, data *model.DataModel) (utils.OKType, error) {
|
||||||
|
data.UserName = username
|
||||||
|
data.AccessInfo = fmt.Sprintf("/data/%s-%s-%d", data.UserName, data.Name, rand.Int())
|
||||||
|
data.Create()
|
||||||
|
return utils.OK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteData(username string, ID uint) (utils.OKType, error) {
|
||||||
|
oldData := model.DataModel{}
|
||||||
|
oldData.SetID(ID)
|
||||||
|
oldData.Fill()
|
||||||
|
if username != oldData.GetUserName() {
|
||||||
|
return nil, errors.New("wrong username")
|
||||||
|
}
|
||||||
|
oldData.Delete()
|
||||||
|
return utils.OK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateData(username string, data *model.DataModel) (utils.OKType, error) {
|
||||||
|
oldData := &model.DataModel{}
|
||||||
|
oldData.SetID(data.ID)
|
||||||
|
oldData.Fill()
|
||||||
|
if username != oldData.GetUserName() {
|
||||||
|
return nil, errors.New("wrong username")
|
||||||
|
}
|
||||||
|
data.Save(oldData)
|
||||||
|
return utils.OK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetData(UserName string, IsPublish bool) (gin.H, error) {
|
||||||
|
data := model.DataModel{}
|
||||||
|
data.UserName = UserName
|
||||||
|
data.IsPublished = IsPublish
|
||||||
|
if UserName == "" && !IsPublish {
|
||||||
|
return gin.H{"data": []string{}}, nil
|
||||||
|
}
|
||||||
|
ans := data.Gets()
|
||||||
|
return gin.H{"data": ans}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddDataController(r *gin.Engine) {
|
||||||
|
utils.RouteWrapper(r, "POST", "/data", AddData, []string{"username", "1"})
|
||||||
|
utils.RouteWrapper(r, "DELETE", "/data", DeleteData, []string{"username", "ID"})
|
||||||
|
utils.RouteWrapper(r, "PUT", "/data", UpdateData, []string{"username", "1"})
|
||||||
|
utils.RouteWrapper(r, "GET", "/data", GetData, []string{"UserName", "IsPublished"})
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"k8s-manager/src/model"
|
"k8s-manager/src/model"
|
||||||
utils "k8s-manager/src/utils"
|
utils "k8s-manager/src/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AddImage(c *gin.Context) {
|
func AddImage(c *gin.Context) {
|
||||||
|
|
@ -20,18 +21,23 @@ func AddImage(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteImage(c *gin.Context) {
|
func DeleteImage(c *gin.Context) {
|
||||||
image := &model.ImageModel{}
|
id := c.Query("ID")
|
||||||
if utils.GinErrorCheck(c, c.BindJSON(image)) {
|
if id == "" {
|
||||||
|
utils.GinError(c, http.StatusBadRequest, "no id")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
username := c.MustGet("username").(string)
|
username := c.MustGet("username").(string)
|
||||||
oldImage := &model.ImageModel{Model: gorm.Model{ID: image.ID}}
|
idi, err := strconv.Atoi(id)
|
||||||
|
if utils.GinErrorCheck(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
oldImage := &model.ImageModel{Model: gorm.Model{ID: uint(idi)}}
|
||||||
oldImage.Fill()
|
oldImage.Fill()
|
||||||
if oldImage.UserName != username {
|
if oldImage.UserName != username {
|
||||||
utils.GinError(c, http.StatusUnauthorized, "username error")
|
utils.GinError(c, http.StatusUnauthorized, "username error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
image.Delete()
|
oldImage.Delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateImage(c *gin.Context) {
|
func UpdateImage(c *gin.Context) {
|
||||||
|
|
@ -49,13 +55,20 @@ func UpdateImage(c *gin.Context) {
|
||||||
image.Save()
|
image.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
func MyImages(c *gin.Context) {
|
func GetImages(c *gin.Context) {
|
||||||
username := c.MustGet("username").(string)
|
username := c.MustGet("username").(string)
|
||||||
myImages := model.GetMyImages(username)
|
var Images []model.ImageModel
|
||||||
c.JSON(http.StatusOK, gin.H{"images": myImages})
|
if c.Query("UserName") != "" {
|
||||||
}
|
if username != c.Query("UserName") {
|
||||||
|
utils.GinError(c, http.StatusUnauthorized, "username error")
|
||||||
func PublicImages(c *gin.Context) {
|
return
|
||||||
myImages := model.GetPublicImages()
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{"images": myImages})
|
Images = model.GetMyImages(username)
|
||||||
|
} else if c.Query("IsPublic") != "" {
|
||||||
|
Images = model.GetPublicImages()
|
||||||
|
} else {
|
||||||
|
utils.GinError(c, http.StatusBadRequest, "param error.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{"data": Images})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"k8s-manager/src/model"
|
||||||
|
"k8s-manager/src/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddProject(username string, proj *model.ProjectModel) (utils.OKType, error) {
|
||||||
|
proj.UserName = username
|
||||||
|
proj.Create()
|
||||||
|
return utils.OK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteProject(username string, ID uint) (utils.OKType, error) {
|
||||||
|
old := &model.ProjectModel{}
|
||||||
|
old.ID = ID
|
||||||
|
old.Fill()
|
||||||
|
if username != old.UserName {
|
||||||
|
return nil, errors.New("wrong username")
|
||||||
|
}
|
||||||
|
old.Delete()
|
||||||
|
return utils.OK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateProject(username string, proj *model.ProjectModel) (utils.OKType, error) {
|
||||||
|
old := &model.ProjectModel{}
|
||||||
|
old.ID = proj.ID
|
||||||
|
old.Fill()
|
||||||
|
if username != old.GetUserName() {
|
||||||
|
return nil, errors.New("wrong username")
|
||||||
|
}
|
||||||
|
proj.Save(old)
|
||||||
|
return utils.OK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProjects(UserName string, IsPublished bool) (gin.H, error) {
|
||||||
|
proj := &model.ProjectModel{}
|
||||||
|
proj.UserName = UserName
|
||||||
|
proj.IsPublished = IsPublished
|
||||||
|
if UserName == "" && !IsPublished {
|
||||||
|
return gin.H{"data": []string{}}, nil
|
||||||
|
}
|
||||||
|
ans := proj.Gets()
|
||||||
|
return gin.H{"data": ans}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddProjectController(r *gin.Engine) {
|
||||||
|
utils.RouteWrapper(r, "POST", "/project", AddProject, []string{"username", "1"})
|
||||||
|
utils.RouteWrapper(r, "DELETE", "/project", DeleteProject, []string{"username", "ID"})
|
||||||
|
utils.RouteWrapper(r, "PUT", "/project", UpdateProject, []string{"username", "1"})
|
||||||
|
utils.RouteWrapper(r, "GET", "/projects", GetProjects, []string{"UserName", "IsPublished"})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import "k8s-manager/src/model"
|
||||||
|
|
||||||
|
func CreateRunningProject(r *model.RunningProject) (*model.RunningProject, error) {
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/net/webdav"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fs *webdav.Handler
|
||||||
|
var fsLock sync.Mutex
|
||||||
|
|
||||||
|
func WebDav(c *gin.Context) {
|
||||||
|
if fs == nil {
|
||||||
|
fsLock.Lock()
|
||||||
|
if fs == nil {
|
||||||
|
fs = &webdav.Handler{
|
||||||
|
Prefix: "/webdav",
|
||||||
|
FileSystem: webdav.Dir("/mnt/CGdata/ms"),
|
||||||
|
LockSystem: webdav.NewMemLS(),
|
||||||
|
Logger: func(h *http.Request, e error) {
|
||||||
|
log.Error(e)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fsLock.Unlock()
|
||||||
|
}
|
||||||
|
log.Info("Webdav ", c.Request.Method, c.Request.URL.Path)
|
||||||
|
fs.ServeHTTP(c.Writer, c.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddWebdavHandler(r *gin.Engine) {
|
||||||
|
methods := []string{
|
||||||
|
"OPTIONS",
|
||||||
|
"GET", "HEAD", "POST",
|
||||||
|
"DELETE",
|
||||||
|
"PUT",
|
||||||
|
"MKCOL",
|
||||||
|
"COPY", "MOVE",
|
||||||
|
"LOCK",
|
||||||
|
"UNLOCK",
|
||||||
|
"PROPFIND",
|
||||||
|
"PROPPATCH",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range methods {
|
||||||
|
r.Handle(m, "/webdav", WebDav)
|
||||||
|
r.Handle(m, "/webdav/*path", WebDav)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -48,6 +48,10 @@ func JWTAuthMiddleware(c *gin.Context) {
|
||||||
// 登录过程,不验证身份
|
// 登录过程,不验证身份
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(c.Request.URL.Path, "/webdav") {
|
||||||
|
// Webdav暂时不验证身份
|
||||||
|
return
|
||||||
|
}
|
||||||
authHeader := c.Request.Header.Get("Authorization")
|
authHeader := c.Request.Header.Get("Authorization")
|
||||||
if authHeader == "" {
|
if authHeader == "" {
|
||||||
var err error
|
var err error
|
||||||
|
|
|
||||||
|
|
@ -1,197 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"k8s-manager/src"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AppModel struct {
|
|
||||||
Id int
|
|
||||||
AppName string
|
|
||||||
UserName string
|
|
||||||
Image string
|
|
||||||
Command string
|
|
||||||
Comment string
|
|
||||||
Env string
|
|
||||||
SvcName string
|
|
||||||
SvcRemovePrefix bool
|
|
||||||
Ports string
|
|
||||||
PersistentPath string
|
|
||||||
OriginId int
|
|
||||||
CreateTime time.Time
|
|
||||||
UpdateTime time.Time
|
|
||||||
IsPublished bool
|
|
||||||
AppType string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*AppModel) TableName() string {
|
|
||||||
return "app"
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetApps(username string) []AppModel {
|
|
||||||
db := k8s_manager.DB()
|
|
||||||
ans := make([]AppModel, 0)
|
|
||||||
db.Where("user_name = ?", username).Find(&ans)
|
|
||||||
return ans
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) Fill() error {
|
|
||||||
db := k8s_manager.DB()
|
|
||||||
res := db.First(a, a.Id)
|
|
||||||
return res.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) fillDefaults() {
|
|
||||||
if a.SvcName == "" {
|
|
||||||
a.SvcName = "svc"
|
|
||||||
}
|
|
||||||
a.SvcName = strings.TrimPrefix(a.SvcName, a.NamePrefix()+"-")
|
|
||||||
if a.PersistentPath == "" {
|
|
||||||
a.PersistentPath = "/data/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) Update() error {
|
|
||||||
a.fillDefaults()
|
|
||||||
a.UpdateTime = time.Now()
|
|
||||||
db := k8s_manager.DB()
|
|
||||||
db.Save(a)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) Create() error {
|
|
||||||
a.fillDefaults()
|
|
||||||
a.CreateTime = time.Now()
|
|
||||||
a.UpdateTime = time.Now()
|
|
||||||
db := k8s_manager.DB()
|
|
||||||
db.Create(a)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) Delete() error {
|
|
||||||
db := k8s_manager.DB()
|
|
||||||
db.Delete(a, a.Id)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) NamePrefix() string {
|
|
||||||
t := "app"
|
|
||||||
if a.OriginId > 0 {
|
|
||||||
t = "fork"
|
|
||||||
}
|
|
||||||
prefix := fmt.Sprintf("%s-%s-%s-%d", t, a.UserName, a.AppName, a.Id)
|
|
||||||
ans := strings.Builder{}
|
|
||||||
for _, c := range prefix {
|
|
||||||
if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' {
|
|
||||||
ans.WriteRune(c)
|
|
||||||
} else {
|
|
||||||
ans.WriteString(fmt.Sprintf("-%d-", c))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ans.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) PodName() string {
|
|
||||||
return fmt.Sprintf("%s-pod", a.NamePrefix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) ContainerName() string {
|
|
||||||
return fmt.Sprintf("%s-container", a.NamePrefix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) ServiceName() string {
|
|
||||||
if len(a.GetPorts()) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(a.SvcName) != "" {
|
|
||||||
return fmt.Sprintf("%s-%s", a.NamePrefix(), a.SvcName)
|
|
||||||
}
|
|
||||||
return a.DefaultServiceName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) DefaultServiceName() string {
|
|
||||||
return fmt.Sprintf("%s-svc", a.NamePrefix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) DeploymentName() string {
|
|
||||||
return fmt.Sprintf("%s-deployment", a.NamePrefix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) GetCommandOrBlack() []string {
|
|
||||||
if strings.TrimSpace(a.Command) != "" {
|
|
||||||
t := a.Command
|
|
||||||
t = strings.ReplaceAll(t, "$URL_PREFIX", "svc/"+a.URLPrefix())
|
|
||||||
return []string{"/bin/sh", "-c", t}
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) GetLabel() (string, string) {
|
|
||||||
return "cg-label", fmt.Sprintf("%s-label", a.NamePrefix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) GetEnv() []v1.EnvVar {
|
|
||||||
if strings.TrimSpace(a.Env) == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sep1 := strings.Split(a.Env, " ")
|
|
||||||
ans := make([]v1.EnvVar, len(sep1))
|
|
||||||
for i, v := range sep1 {
|
|
||||||
kv := strings.SplitN(v, "=", 2)
|
|
||||||
ans[i].Name = kv[0]
|
|
||||||
if len(kv) == 2 {
|
|
||||||
ans[i].Value = kv[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ans
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) GetPorts() []int32 {
|
|
||||||
ans := make([]int32, 0)
|
|
||||||
var cur int32 = 0
|
|
||||||
for _, c := range a.Ports {
|
|
||||||
if unicode.IsNumber(c) {
|
|
||||||
cur = cur*10 + (c - '0')
|
|
||||||
} else if cur > 0 {
|
|
||||||
ans = append(ans, cur)
|
|
||||||
cur = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cur > 0 {
|
|
||||||
ans = append(ans, cur)
|
|
||||||
}
|
|
||||||
return ans
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) IngressName() string {
|
|
||||||
return fmt.Sprintf("%s-ingress", a.NamePrefix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) URLPrefix() string {
|
|
||||||
return a.ServiceName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) PersistentVolumeName() string {
|
|
||||||
return a.UserName + "-pv"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) NFSPath() string {
|
|
||||||
return k8s_manager.GetConfig().NFSPath + "/" + a.UserName + "/" + a.AppName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) PersistentVolumeClaimName() string {
|
|
||||||
return a.NamePrefix() + "-pvc"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) StorageClassName() string {
|
|
||||||
return a.NamePrefix() + "-storage-class"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppModel) PersistentVolumeMountName() string {
|
|
||||||
return a.NamePrefix() + "-pvm"
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
k8s_manager "k8s-manager/src"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ModalType interface {
|
||||||
|
DataModel | ImageModel
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModalInterface[T ModalType] interface {
|
||||||
|
*T
|
||||||
|
Create()
|
||||||
|
Fill()
|
||||||
|
Delete()
|
||||||
|
Save()
|
||||||
|
Gets(map[string]interface{}) []any
|
||||||
|
|
||||||
|
GetUserName() string
|
||||||
|
SetUserName(string)
|
||||||
|
SetID(uint)
|
||||||
|
GetID() uint
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseModel[T any] struct {
|
||||||
|
gorm.Model
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseModel[T]) Fill() {
|
||||||
|
db := k8s_manager.DB()
|
||||||
|
p := (*T)(unsafe.Pointer(b))
|
||||||
|
db.Preload(clause.Associations).First(p, b.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseModel[T]) Create() {
|
||||||
|
db := k8s_manager.DB()
|
||||||
|
p := (*T)(unsafe.Pointer(b))
|
||||||
|
db.Create(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseModel[T]) Save(old *T) {
|
||||||
|
db := k8s_manager.DB()
|
||||||
|
p := (*T)(unsafe.Pointer(b))
|
||||||
|
db.Save(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseModel[T]) Delete() {
|
||||||
|
db := k8s_manager.DB()
|
||||||
|
p := (*T)(unsafe.Pointer(b))
|
||||||
|
db.Delete(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseModel[T]) Gets() []T {
|
||||||
|
db := k8s_manager.DB()
|
||||||
|
ans := make([]T, 0)
|
||||||
|
p := (*T)(unsafe.Pointer(b))
|
||||||
|
db.Preload(clause.Associations).Where(p).Find(&ans)
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (b *BaseModel[T]) Get(query map[string]interface{}) *T {
|
||||||
|
// db := k8s_manager.DB()
|
||||||
|
// p := (*T)(unsafe.Pointer(b))
|
||||||
|
// db.Where(query).First(p)
|
||||||
|
// return p
|
||||||
|
//}
|
||||||
|
|
||||||
|
func (b *BaseModel[T]) SetUserName(name string) {
|
||||||
|
p := (*T)(unsafe.Pointer(b))
|
||||||
|
a := reflect.ValueOf(p)
|
||||||
|
reflect.Indirect(a).FieldByName("UserName").SetString(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseModel[T]) SetID(ID uint) {
|
||||||
|
p := (*T)(unsafe.Pointer(b))
|
||||||
|
a := reflect.ValueOf(p)
|
||||||
|
reflect.Indirect(a).FieldByName("ID").SetUint(uint64(ID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseModel[T]) GetUserName() string {
|
||||||
|
p := (*T)(unsafe.Pointer(b))
|
||||||
|
a := reflect.ValueOf(p)
|
||||||
|
return reflect.Indirect(a).FieldByName("UserName").String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseModel[T]) GetID() uint {
|
||||||
|
p := (*T)(unsafe.Pointer(b))
|
||||||
|
a := reflect.ValueOf(p)
|
||||||
|
return uint(reflect.Indirect(a).FieldByName("ID").Uint())
|
||||||
|
}
|
||||||
|
|
@ -1,23 +1,16 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
|
||||||
"gorm.io/gorm"
|
|
||||||
k8s_manager "k8s-manager/src"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DataModel struct {
|
type DataModel struct {
|
||||||
gorm.Model
|
BaseModel[DataModel]
|
||||||
Type string
|
Type string
|
||||||
Name string
|
Name string
|
||||||
Comment string
|
Comment string
|
||||||
AccessInfo string
|
Doc string
|
||||||
|
UserName string
|
||||||
|
IsPublished bool
|
||||||
|
AccessInfo string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*DataModel) TableName() string {
|
func (*DataModel) TableName() string {
|
||||||
return "data"
|
return "data"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DataModel) Fill() {
|
|
||||||
db := k8s_manager.DB()
|
|
||||||
db.First(d, d.ID)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
type ImageModel struct {
|
type ImageModel struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
|
Name string
|
||||||
Image string
|
Image string
|
||||||
Comment string
|
Comment string
|
||||||
Command string
|
Command string
|
||||||
|
|
@ -14,6 +15,7 @@ type ImageModel struct {
|
||||||
Port string
|
Port string
|
||||||
UserName string
|
UserName string
|
||||||
IsPublished bool
|
IsPublished bool
|
||||||
|
Doc string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*ImageModel) TableName() string {
|
func (*ImageModel) TableName() string {
|
||||||
|
|
@ -40,6 +42,13 @@ func (i *ImageModel) Delete() {
|
||||||
db.Delete(i)
|
db.Delete(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetImages(query map[string]interface{}) []ImageModel {
|
||||||
|
db := k8s_manager.DB()
|
||||||
|
ans := make([]ImageModel, 0)
|
||||||
|
db.Where(query).Find(&ans)
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
func GetMyImages(username string) []ImageModel {
|
func GetMyImages(username string) []ImageModel {
|
||||||
db := k8s_manager.DB()
|
db := k8s_manager.DB()
|
||||||
ans := make([]ImageModel, 0)
|
ans := make([]ImageModel, 0)
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,272 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gorm.io/gorm"
|
"fmt"
|
||||||
|
k8s_manager "k8s-manager/src"
|
||||||
|
"k8s-manager/src/utils"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProjectModel struct {
|
type ProjectModel struct {
|
||||||
gorm.Model
|
BaseModel[ProjectModel]
|
||||||
ProjectName string
|
ProjectName string
|
||||||
UserName string
|
UserName string
|
||||||
GroupName string
|
GroupName string
|
||||||
Type string
|
Type string
|
||||||
Comment string
|
Comment string
|
||||||
|
Doc string
|
||||||
|
IsPublished bool
|
||||||
|
|
||||||
ImageID int
|
ImageID int
|
||||||
Image ImageModel
|
Image ImageModel
|
||||||
TrainData []*DataModel `gorm:"many2many:projects_data"`
|
TrainData []*ProjectTrainDataModel `gorm:"foreignKey:ProjectID"`
|
||||||
|
|
||||||
|
UserPath string
|
||||||
|
CodePath string
|
||||||
|
ModelPath string
|
||||||
|
|
||||||
|
Running bool `gorm:"-:all"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*ProjectModel) TableName() string {
|
func (*ProjectModel) TableName() string {
|
||||||
return "projects"
|
return "projects"
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunningProject struct {
|
type ProjectTrainDataModel struct {
|
||||||
|
BaseModel[ProjectTrainDataModel]
|
||||||
|
ProjectID int
|
||||||
Project *ProjectModel
|
Project *ProjectModel
|
||||||
StartTime *time.Time
|
DataID int
|
||||||
UserName string
|
Data *DataModel
|
||||||
Nodes int
|
MountPath string
|
||||||
Cpus int
|
}
|
||||||
Memory int
|
|
||||||
Gpus int
|
func (*ProjectTrainDataModel) TableName() string {
|
||||||
GpuMemory int
|
return "project_train_data"
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (p *ProjectModel) Fill() {
|
||||||
|
// db := k8s_manager.DB()
|
||||||
|
// db.Preload(clause.Associations).First(p, p.ID)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (p *ProjectModel) Create() {
|
||||||
|
// db := k8s_manager.DB()
|
||||||
|
// db.Create(p)
|
||||||
|
//}
|
||||||
|
|
||||||
|
func (p *ProjectModel) Delete() {
|
||||||
|
db := k8s_manager.DB()
|
||||||
|
for _, d := range p.TrainData {
|
||||||
|
db.Delete(d)
|
||||||
|
}
|
||||||
|
db.Delete(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ProjectModel) Save(oldProject *ProjectModel) {
|
||||||
|
db := k8s_manager.DB()
|
||||||
|
//oldProject := &ProjectModel{}
|
||||||
|
//oldProject.ID = p.ID
|
||||||
|
//oldProject.Fill()
|
||||||
|
|
||||||
|
//toAdd := utils.Difference(p.TrainData, oldProject.TrainData, func(a, b ProjectTrainDataModel) bool { return a.ID == b.ID })
|
||||||
|
//for t := range toAdd {
|
||||||
|
// db.Create(t)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
toDel := utils.Difference(oldProject.TrainData, p.TrainData, func(a, b *ProjectTrainDataModel) bool { return a.ID == b.ID })
|
||||||
|
for _, t := range toDel {
|
||||||
|
db.Delete(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
toSave := utils.Intersect(p.TrainData, oldProject.TrainData, func(a, b *ProjectTrainDataModel) bool { return a.ID == b.ID })
|
||||||
|
for _, t := range toSave {
|
||||||
|
t.Project = p
|
||||||
|
db.Save(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Save(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (p *ProjectModel) Gets() []ProjectModel {
|
||||||
|
// db := k8s_manager.DB()
|
||||||
|
// var ans []ProjectModel
|
||||||
|
// db.Model(&ProjectModel{}).Preload(clause.Associations).Where(p).Find(&ans)
|
||||||
|
// return ans
|
||||||
|
//}
|
||||||
|
|
||||||
|
type RunningProject struct {
|
||||||
|
Project *ProjectModel
|
||||||
|
StartTime *time.Time
|
||||||
|
UserName string
|
||||||
|
Nodes int
|
||||||
|
Cpus int
|
||||||
|
Memory int
|
||||||
|
Gpus int
|
||||||
|
GpuMemory int
|
||||||
|
URL string
|
||||||
|
ControlURL string
|
||||||
|
|
||||||
|
ID int
|
||||||
|
SvcRemovePrefix bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) NamePrefix() string {
|
||||||
|
t := "app"
|
||||||
|
prefix := fmt.Sprintf("%s-%s-%s-%d", t, a.UserName, a.Project.ProjectName, a.ID)
|
||||||
|
ans := strings.Builder{}
|
||||||
|
for _, c := range prefix {
|
||||||
|
if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' {
|
||||||
|
ans.WriteRune(c)
|
||||||
|
} else {
|
||||||
|
ans.WriteString(fmt.Sprintf("-%d-", c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) PodName() string {
|
||||||
|
return fmt.Sprintf("%s-pod", a.NamePrefix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) ContainerName() string {
|
||||||
|
return fmt.Sprintf("%s-container", a.NamePrefix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) ServiceName() string {
|
||||||
|
if len(a.GetPorts()) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return a.DefaultServiceName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) DefaultServiceName() string {
|
||||||
|
return fmt.Sprintf("%s-svc", a.NamePrefix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) DeploymentName() string {
|
||||||
|
return fmt.Sprintf("%s-deployment", a.NamePrefix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) GetCommandOrBlack() []string {
|
||||||
|
if strings.TrimSpace(a.Project.Image.Command) != "" {
|
||||||
|
t := a.Project.Image.Command
|
||||||
|
t = strings.ReplaceAll(t, "$URL_PREFIX", "svc/"+a.URLPrefix())
|
||||||
|
return []string{"/bin/sh", "-c", t}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) GetLabel() (string, string) {
|
||||||
|
return "cg-label", fmt.Sprintf("%s-label", a.NamePrefix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) GetEnv() []v1.EnvVar {
|
||||||
|
if strings.TrimSpace(a.Project.Image.Env) == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sep1 := strings.Split(a.Project.Image.Env, " ")
|
||||||
|
ans := make([]v1.EnvVar, len(sep1))
|
||||||
|
for i, v := range sep1 {
|
||||||
|
kv := strings.SplitN(v, "=", 2)
|
||||||
|
ans[i].Name = kv[0]
|
||||||
|
if len(kv) == 2 {
|
||||||
|
ans[i].Value = kv[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) GetPorts() []int32 {
|
||||||
|
ans := make([]int32, 0)
|
||||||
|
var cur int32 = 0
|
||||||
|
for _, c := range a.Project.Image.Port {
|
||||||
|
if unicode.IsNumber(c) {
|
||||||
|
cur = cur*10 + (c - '0')
|
||||||
|
} else if cur > 0 {
|
||||||
|
ans = append(ans, cur)
|
||||||
|
cur = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cur > 0 {
|
||||||
|
ans = append(ans, cur)
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) IngressName() string {
|
||||||
|
return fmt.Sprintf("%s-ingress", a.NamePrefix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) URLPrefix() string {
|
||||||
|
return a.ServiceName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) PersistentVolumeName() string {
|
||||||
|
return a.UserName + "-pv"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) NFSPath() string {
|
||||||
|
return k8s_manager.GetConfig().NFSPath + "/" + a.UserName + "/" + a.Project.ProjectName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) PersistentVolumeClaimName() string {
|
||||||
|
return a.NamePrefix() + "-pvc"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) StorageClassName() string {
|
||||||
|
return a.NamePrefix() + "-storage-class"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) PersistentVolumeMountName() string {
|
||||||
|
return a.NamePrefix() + "-pvm"
|
||||||
|
}
|
||||||
|
|
||||||
|
type NFSMount struct {
|
||||||
|
Name string
|
||||||
|
From string
|
||||||
|
To string
|
||||||
|
ReadOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) createNFSMount(name string, from string, to string, readonly bool) NFSMount {
|
||||||
|
return NFSMount{
|
||||||
|
Name: fmt.Sprintf("%s-nfs-%s", a.NamePrefix(), name),
|
||||||
|
From: from,
|
||||||
|
To: to,
|
||||||
|
ReadOnly: readonly,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RunningProject) GetNFSMounts() []NFSMount {
|
||||||
|
var ans []NFSMount
|
||||||
|
if a.Project.UserPath != "" {
|
||||||
|
ans = append(ans, a.createNFSMount("user",
|
||||||
|
fmt.Sprintf("/users/%s/home", a.UserName), a.Project.UserPath, false))
|
||||||
|
}
|
||||||
|
if a.Project.CodePath != "" {
|
||||||
|
ans = append(ans, a.createNFSMount("code",
|
||||||
|
fmt.Sprintf("/users/%s/code/%s", a.UserName, a.Project.ProjectName), a.Project.CodePath, false))
|
||||||
|
}
|
||||||
|
if a.Project.CodePath != "" {
|
||||||
|
ans = append(ans, a.createNFSMount("model",
|
||||||
|
fmt.Sprintf("/users/%s/model/%s", a.UserName, a.Project.ProjectName), a.Project.ModelPath, false))
|
||||||
|
}
|
||||||
|
for i, m := range a.Project.TrainData {
|
||||||
|
if m.Data.Type != "dir" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ans = append(ans, a.createNFSMount(
|
||||||
|
fmt.Sprintf("%s-%d", m.Data.Name, i),
|
||||||
|
m.Data.AccessInfo,
|
||||||
|
m.MountPath,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return ans
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,24 +73,53 @@ func (c *K8sClient) DeploymentExists(deploymentName string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *K8sClient) UpdateDeployment(app *model.AppModel) {
|
func checkDirs(mounts []model.NFSMount) {
|
||||||
labelKey, labelValue := app.GetLabel()
|
for _, n := range mounts {
|
||||||
l := map[string]string{labelKey: labelValue}
|
if _, err := os.Stat(n.From); os.IsNotExist(err) {
|
||||||
if _, err := os.Stat(app.NFSPath()); os.IsNotExist(err) {
|
err := os.MkdirAll(n.From, 0777)
|
||||||
err := os.MkdirAll(app.NFSPath(), 0777)
|
if err != nil {
|
||||||
if err != nil {
|
log.WithError(err).Error("Make nfs dir error")
|
||||||
log.WithError(err).Error("Make nfs dir error")
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *K8sClient) UpdateDeployment(app *model.RunningProject) {
|
||||||
|
labelKey, labelValue := app.GetLabel()
|
||||||
|
l := map[string]string{labelKey: labelValue}
|
||||||
|
nfsDirs := app.GetNFSMounts()
|
||||||
|
checkDirs(nfsDirs)
|
||||||
|
var volumeMounts []v1.VolumeMount
|
||||||
|
var volumes []v1.Volume
|
||||||
|
for _, n := range nfsDirs {
|
||||||
|
volumeMounts = append(volumeMounts, v1.VolumeMount{
|
||||||
|
Name: n.Name,
|
||||||
|
MountPath: n.To,
|
||||||
|
ReadOnly: n.ReadOnly,
|
||||||
|
})
|
||||||
|
volumes = append(volumes, v1.Volume{
|
||||||
|
Name: n.Name,
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
NFS: &v1.NFSVolumeSource{
|
||||||
|
Server: k8s_manager.GetConfig().NFSPath,
|
||||||
|
Path: n.From,
|
||||||
|
ReadOnly: n.ReadOnly,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
podLabels := map[string]string{
|
podLabels := map[string]string{
|
||||||
labelKey: labelValue,
|
labelKey: labelValue,
|
||||||
"cg-deployment": app.DeploymentName(),
|
"cg-deployment": app.DeploymentName(),
|
||||||
}
|
}
|
||||||
|
|
||||||
containerPorts := funk.Map(app.GetPorts(), func(port int32) v1.ContainerPort {
|
containerPorts := funk.Map(app.GetPorts(), func(port int32) v1.ContainerPort {
|
||||||
return v1.ContainerPort{
|
return v1.ContainerPort{
|
||||||
ContainerPort: port,
|
ContainerPort: port,
|
||||||
}
|
}
|
||||||
}).([]v1.ContainerPort)
|
}).([]v1.ContainerPort)
|
||||||
|
|
||||||
deployment := &appv1.Deployment{
|
deployment := &appv1.Deployment{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: app.DeploymentName(),
|
Name: app.DeploymentName(),
|
||||||
|
|
@ -108,36 +137,19 @@ func (c *K8sClient) UpdateDeployment(app *model.AppModel) {
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{{
|
Containers: []v1.Container{{
|
||||||
Name: app.ContainerName(),
|
Name: app.ContainerName(),
|
||||||
Image: app.Image,
|
Image: app.Project.Image.Image,
|
||||||
Command: app.GetCommandOrBlack(),
|
Command: app.GetCommandOrBlack(),
|
||||||
Env: append(app.GetEnv(), v1.EnvVar{
|
Env: append(app.GetEnv(), v1.EnvVar{
|
||||||
Name: "URL_PREFIX",
|
Name: "URL_PREFIX",
|
||||||
Value: app.URLPrefix(),
|
Value: app.URLPrefix(),
|
||||||
}),
|
}),
|
||||||
VolumeMounts: []v1.VolumeMount{
|
VolumeMounts: volumeMounts,
|
||||||
{
|
Ports: containerPorts,
|
||||||
Name: app.PersistentVolumeMountName(),
|
|
||||||
ReadOnly: false,
|
|
||||||
MountPath: app.PersistentPath,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Ports: containerPorts,
|
|
||||||
SecurityContext: &v1.SecurityContext{
|
SecurityContext: &v1.SecurityContext{
|
||||||
RunAsUser: utils.UnnamedPointer(int64(0)),
|
RunAsUser: utils.UnnamedPointer(int64(0)),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
Volumes: []v1.Volume{
|
Volumes: volumes,
|
||||||
{
|
|
||||||
Name: app.PersistentVolumeMountName(),
|
|
||||||
VolumeSource: v1.VolumeSource{
|
|
||||||
NFS: &v1.NFSVolumeSource{
|
|
||||||
Server: k8s_manager.GetConfig().NFSHost,
|
|
||||||
Path: app.NFSPath(),
|
|
||||||
ReadOnly: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -166,7 +178,7 @@ func (c *K8sClient) ServiceExists(servername string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *K8sClient) UpdateService(app *model.AppModel) {
|
func (c *K8sClient) UpdateService(app *model.RunningProject) {
|
||||||
ports := app.GetPorts()
|
ports := app.GetPorts()
|
||||||
if len(ports) == 0 {
|
if len(ports) == 0 {
|
||||||
return
|
return
|
||||||
|
|
@ -210,19 +222,19 @@ func (c *K8sClient) UpdateService(app *model.AppModel) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *K8sClient) CreateApp(app *model.AppModel) {
|
func (c *K8sClient) CreateApp(app *model.RunningProject) {
|
||||||
c.UpdateDeployment(app)
|
c.UpdateDeployment(app)
|
||||||
c.UpdateService(app)
|
c.UpdateService(app)
|
||||||
c.UpdateIngress(app)
|
c.UpdateIngress(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *K8sClient) DeleteApp(app *model.AppModel) {
|
func (c *K8sClient) DeleteApp(app *model.RunningProject) {
|
||||||
c.DeleteDeployment(app.DeploymentName())
|
c.DeleteDeployment(app.DeploymentName())
|
||||||
c.DeleteAppService(app)
|
c.DeleteAppService(app)
|
||||||
c.DeleteIngress(app.IngressName())
|
c.DeleteIngress(app.IngressName())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *K8sClient) DeleteAppService(app *model.AppModel) {
|
func (c *K8sClient) DeleteAppService(app *model.RunningProject) {
|
||||||
k, v := app.GetLabel()
|
k, v := app.GetLabel()
|
||||||
label := labels.FormatLabels(map[string]string{k: v})
|
label := labels.FormatLabels(map[string]string{k: v})
|
||||||
svcs, err := c.CoreV1().Services(c.Namespace).List(context.Background(), metav1.ListOptions{
|
svcs, err := c.CoreV1().Services(c.Namespace).List(context.Background(), metav1.ListOptions{
|
||||||
|
|
@ -294,7 +306,7 @@ func (c *K8sClient) IngressExists(name string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *K8sClient) UpdateIngress(a *model.AppModel) {
|
func (c *K8sClient) UpdateIngress(a *model.RunningProject) {
|
||||||
ports := a.GetPorts()
|
ports := a.GetPorts()
|
||||||
if len(ports) == 0 {
|
if len(ports) == 0 {
|
||||||
return
|
return
|
||||||
|
|
@ -363,7 +375,7 @@ func (c *K8sClient) DeleteIngress(name string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
func (c *K8sClient) PersistentVolumeExists(app *AppModel) bool {
|
func (c *K8sClient) PersistentVolumeExists(app *RunningProject) bool {
|
||||||
_, err := c.CoreV1().PersistentVolumes().Get(context.Background(), app.PersistentVolumeName(), metav1.GetOptions{})
|
_, err := c.CoreV1().PersistentVolumes().Get(context.Background(), app.PersistentVolumeName(), metav1.GetOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return true
|
return true
|
||||||
|
|
@ -374,7 +386,7 @@ func (c *K8sClient) PersistentVolumeExists(app *AppModel) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *K8sClient) CreatePersistentVolume(app *AppModel) {
|
func (c *K8sClient) CreatePersistentVolume(app *RunningProject) {
|
||||||
cap1, _ := resource.ParseQuantity("1Gi")
|
cap1, _ := resource.ParseQuantity("1Gi")
|
||||||
pv := &v1.PersistentVolume{
|
pv := &v1.PersistentVolume{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
|
@ -404,7 +416,7 @@ func (c *K8sClient) CreatePersistentVolume(app *AppModel) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *K8sClient) PersistentVolumeClaimExists(app *AppModel) bool {
|
func (c *K8sClient) PersistentVolumeClaimExists(app *RunningProject) bool {
|
||||||
_, err := c.CoreV1().PersistentVolumeClaims(c.Namespace).Get(context.Background(), app.PersistentVolumeClaimName(), metav1.GetOptions{})
|
_, err := c.CoreV1().PersistentVolumeClaims(c.Namespace).Get(context.Background(), app.PersistentVolumeClaimName(), metav1.GetOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return true
|
return true
|
||||||
|
|
@ -415,7 +427,7 @@ func (c *K8sClient) PersistentVolumeClaimExists(app *AppModel) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *K8sClient) CreatePersistentVolumeClaim(app *AppModel) {
|
func (c *K8sClient) CreatePersistentVolumeClaim(app *RunningProject) {
|
||||||
pvc := &v1.PersistentVolumeClaim{
|
pvc := &v1.PersistentVolumeClaim{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: app.PersistentVolumeClaimName(),
|
Name: app.PersistentVolumeClaimName(),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
func Difference[T any](a, b []T, com func(T, T) bool) []T {
|
||||||
|
var ans []T
|
||||||
|
for _, x := range a {
|
||||||
|
unique := true
|
||||||
|
for _, y := range b {
|
||||||
|
if com(x, y) {
|
||||||
|
unique = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if unique {
|
||||||
|
ans = append(ans, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
func Intersect[T any](a, b []T, com func(T, T) bool) []T {
|
||||||
|
var ans []T
|
||||||
|
for _, x := range a {
|
||||||
|
for _, y := range b {
|
||||||
|
if com(x, y) {
|
||||||
|
ans = append(ans, x)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UnnamedPointer[T any](v T) *T {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func GinError(c *gin.Context, code int, msg string) {
|
||||||
|
c.JSON(code, gin.H{"ok": false, "msg": msg})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GinOk(c *gin.Context, msg string) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"ok": true, "msg": msg})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GinOkData(c *gin.Context, data interface{}) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"ok": true, "data": data})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GinErrorCheck(c *gin.Context, err error) bool {
|
||||||
|
if err != nil {
|
||||||
|
GinError(c, http.StatusBadRequest, err.Error())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func setValue(data string, value reflect.Value) error {
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
v, err := strconv.ParseBool(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value.SetBool(v)
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
v, err := strconv.ParseInt(data, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value.SetInt(v)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
v, err := strconv.ParseUint(data, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value.SetUint(v)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
v, err := strconv.ParseFloat(data, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value.SetFloat(v)
|
||||||
|
case reflect.Pointer:
|
||||||
|
return setValue(data, reflect.Indirect(value))
|
||||||
|
case reflect.String:
|
||||||
|
value.SetString(data)
|
||||||
|
case reflect.Struct:
|
||||||
|
err := json.Unmarshal([]byte(data), value.Pointer())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Errorf("utils.setValue unsupported type %s", value.Type().Name())
|
||||||
|
return errors.New("unsupported type")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type OKType = map[string]any
|
||||||
|
|
||||||
|
var OK = OKType{"ok": true}
|
||||||
|
|
||||||
|
func routeWrapperHandler(handler any, names []string) func(ctx *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
handlerFunc := reflect.ValueOf(handler)
|
||||||
|
handlerFuncType := handlerFunc.Type()
|
||||||
|
handlerFuncParamLen := handlerFuncType.NumIn()
|
||||||
|
if handlerFuncParamLen != len(names) {
|
||||||
|
panic("Handler func Param len != len(names)")
|
||||||
|
}
|
||||||
|
inputArgs := make([]reflect.Value, handlerFuncParamLen)
|
||||||
|
for i := 0; i < handlerFuncParamLen; i += 1 {
|
||||||
|
paraType := handlerFuncType.In(i)
|
||||||
|
var paraValue reflect.Value
|
||||||
|
if paraType.Kind() == reflect.Pointer {
|
||||||
|
paraValue = reflect.New(paraType.Elem())
|
||||||
|
} else {
|
||||||
|
paraValue = reflect.New(paraType)
|
||||||
|
}
|
||||||
|
v, ok := c.Get(names[i])
|
||||||
|
if ok {
|
||||||
|
t := reflect.ValueOf(v)
|
||||||
|
paraValue.Elem().Set(t)
|
||||||
|
} else {
|
||||||
|
vs := c.Param(names[i])
|
||||||
|
if vs == "" {
|
||||||
|
vs = c.Query(names[i])
|
||||||
|
}
|
||||||
|
if vs != "" {
|
||||||
|
err := setValue(vs, paraValue)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Cannot set value")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if paraValue.Kind() == reflect.Struct || (paraType.Kind() == reflect.Pointer && paraType.Elem().Kind() == reflect.Struct) {
|
||||||
|
err := c.BindJSON(paraValue.Interface())
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"ok": false,
|
||||||
|
"msg": fmt.Sprintf("Cannot BindJson %s: %s", paraType.Name(), err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if paraType.Kind() == reflect.Pointer {
|
||||||
|
inputArgs[i] = paraValue
|
||||||
|
} else {
|
||||||
|
inputArgs[i] = paraValue.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out := handlerFunc.Call(inputArgs)
|
||||||
|
var ret = out[0].Interface()
|
||||||
|
if out[1].Interface() != nil {
|
||||||
|
var err = out[1].Interface().(error)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"ok": false, "msg": err.Error()})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RouteWrapper(r *gin.Engine, method string, path string, handler any, names []string) {
|
||||||
|
r.Handle(method, path, routeWrapperHandler(handler, names))
|
||||||
|
}
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func UnnamedPointer[T any](v T) *T {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
func GinError(c *gin.Context, code int, msg string) {
|
|
||||||
c.JSON(code, gin.H{"ok": false, "msg": msg})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GinOk(c *gin.Context, msg string) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{"ok": true, "msg": msg})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GinOkData(c *gin.Context, data interface{}) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{"ok": true, "data": data})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GinErrorCheck(c *gin.Context, err error) bool {
|
|
||||||
if err != nil {
|
|
||||||
GinError(c, http.StatusBadRequest, err.Error())
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
//controller.BaseController[model.DataModel, *model.DataModel]{}.Add(nil)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"k8s-manager/src/model"
|
||||||
|
"k8s-manager/src/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DataHandler(model model.DataModel, q1 string, q2 int) (map[string]any, error) {
|
||||||
|
s, _ := json.Marshal(model)
|
||||||
|
fmt.Println(string(s))
|
||||||
|
fmt.Printf("Call DataHandler %s %d\n", q1, q2)
|
||||||
|
return gin.H{"name": model.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
utils.RouteWrapper(r, "GET", "/index", DataHandler, []string{"model", "q1", "q2"})
|
||||||
|
err := r.Run(":9000")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
k8s_manager "k8s-manager/src"
|
k8s_manager "k8s-manager/src"
|
||||||
"k8s-manager/src/model"
|
"k8s-manager/src/model"
|
||||||
|
|
@ -10,9 +9,10 @@ import (
|
||||||
func CreateTables() {
|
func CreateTables() {
|
||||||
db := k8s_manager.DB()
|
db := k8s_manager.DB()
|
||||||
err := db.Migrator().AutoMigrate(
|
err := db.Migrator().AutoMigrate(
|
||||||
&model.ProjectModel{},
|
//&model.ProjectModel{},
|
||||||
&model.ImageModel{},
|
//&model.ImageModel{},
|
||||||
&model.DataModel{},
|
//&model.DataModel{},
|
||||||
|
&model.ProjectTrainDataModel{},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|
@ -22,9 +22,10 @@ func CreateTables() {
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
k8s_manager.InitConfig()
|
k8s_manager.InitConfig()
|
||||||
db := k8s_manager.DB()
|
CreateTables()
|
||||||
p := model.ProjectModel{}
|
// db := k8s_manager.DB()
|
||||||
db.Preload("TrainData").Where("user_name = ?", "wmj").Find(&p)
|
// p := model.ProjectModel{}
|
||||||
j, _ := json.Marshal(&p)
|
// db.Preload("TrainData").Where("user_name = ?", "wmj").Find(&p)
|
||||||
fmt.Println(string(j))
|
// j, _ := json.Marshal(&p)
|
||||||
|
// fmt.Println(string(j))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
FROM ugeek/webdav:amd64
|
||||||
|
|
||||||
|
COPY entrypoint.sh /
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
TAG = docker.educg.net/cg/webdav:v1.0
|
||||||
|
|
||||||
|
build:
|
||||||
|
docker build -t $(TAG) .
|
||||||
|
|
||||||
|
push:
|
||||||
|
docker push $(TAG)
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ -n "$USERNAME" ] && [ -n "$PASSWORD" ]
|
||||||
|
then
|
||||||
|
htpasswd -bc /etc/nginx/htpasswd $USERNAME $PASSWORD
|
||||||
|
echo Done.
|
||||||
|
else
|
||||||
|
echo Using no auth.
|
||||||
|
sed -i 's%auth_basic "Restricted";% %g' /etc/nginx/conf.d/default.conf
|
||||||
|
sed -i 's%auth_basic_user_file htpasswd;% %g' /etc/nginx/conf.d/default.conf
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: cg-webdav
|
||||||
|
labels:
|
||||||
|
app: cg-webdav
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: cg-webdav
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: cg-webdav
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: cg-webdav-container
|
||||||
|
# image: bytemark/webdav:2.4
|
||||||
|
image: docker.educg.net/cg/webdav:v1.0
|
||||||
|
# command: ["/bin/bash"]
|
||||||
|
# args: ["-c", "sleep 365d"]
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
env:
|
||||||
|
- name: TZ
|
||||||
|
value: Asia/Shanghai
|
||||||
|
volumeMounts:
|
||||||
|
- name: webdav-nfs
|
||||||
|
mountPath: /media
|
||||||
|
# mountPath: /var/lib/dav
|
||||||
|
volumes:
|
||||||
|
- name: webdav-nfs
|
||||||
|
nfs:
|
||||||
|
server: 192.168.252.250
|
||||||
|
path: /mnt/CGdata/ms
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: cg-webdav-svc
|
||||||
|
labels:
|
||||||
|
name: cg-webdav-svc
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
selector:
|
||||||
|
app: cg-webdav
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: webdav-ingress
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||||
|
nginx.ingress.kubernetes.io/proxy-body-size: 10g
|
||||||
|
nginx.ingress.kubernetes.io/proxy-buffering: "on"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-buffer-size: "1024k"
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- http:
|
||||||
|
paths:
|
||||||
|
- path: /files(/|$)(.*)
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: cg-webdav-svc
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
Loading…
Reference in New Issue