Files
devstar/routers/web/devcontainer/devcontainer.go

297 lines
10 KiB
Go
Raw Normal View History

2025-08-21 21:31:51 +08:00
package devcontainer
2025-08-11 16:32:10 +08:00
import (
"encoding/json"
"fmt"
"io"
"net/http"
"path"
"strconv"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2025-08-16 18:31:14 +08:00
"code.gitea.io/gitea/modules/templates"
2025-08-11 16:32:10 +08:00
"code.gitea.io/gitea/services/context"
devcontainer_service "code.gitea.io/gitea/services/devcontainer"
)
const (
2025-08-16 18:31:14 +08:00
tplGetDevContainerDetails templates.TplName = "repo/devcontainer/details"
2025-08-11 16:32:10 +08:00
)
// 获取仓库 Dev Container 详细信息
2025-08-16 18:31:14 +08:00
// GET /{username}/{reponame}/devcontainer
2025-08-11 16:32:10 +08:00
func GetDevContainerDetails(ctx *context.Context) {
2025-08-21 21:31:51 +08:00
if ctx.Doer == nil {
ctx.HTML(http.StatusForbidden, "")
return
}
2025-08-16 18:31:14 +08:00
var err error
2025-08-29 16:08:02 +08:00
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
2025-09-18 17:57:29 +08:00
log.Info("setting.CustomConf %s", setting.CustomConf)
log.Info("cfg.Section().Key().Value() %s", cfg.Section("server").Key("ROOT_URL").Value())
2025-08-29 16:08:02 +08:00
if err != nil {
ctx.Flash.Error(err.Error(), true)
}
2025-09-18 19:25:17 +08:00
2025-08-16 18:31:14 +08:00
ctx.Data["isAdmin"], err = devcontainer_service.IsAdmin(ctx, ctx.Doer, ctx.Repo.Repository.ID)
if err != nil {
log.Info(err.Error())
ctx.Flash.Error(err.Error(), true)
}
ctx.Data["HasDevContainer"], err = devcontainer_service.HasDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
if err != nil {
log.Info(err.Error())
ctx.Flash.Error(err.Error(), true)
}
ctx.Data["ValidateDevContainerConfiguration"] = true
ctx.Data["HasDevContainerConfiguration"], err = devcontainer_service.HasDevContainerConfiguration(ctx, ctx.Repo)
if err != nil {
log.Info(err.Error())
ctx.Data["ValidateDevContainerConfiguration"] = false
ctx.Flash.Error(err.Error(), true)
}
if ctx.Data["HasDevContainerConfiguration"] == false {
ctx.Data["ValidateDevContainerConfiguration"] = false
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
ctx.Data["HasDevContainerDockerfile"], err = devcontainer_service.HasDevContainerDockerFile(ctx, ctx.Repo)
if err != nil {
log.Info(err.Error())
ctx.Flash.Error(err.Error(), true)
}
if ctx.Data["HasDevContainer"] == true {
configurationString, _ := devcontainer_service.GetDevcontainerConfigurationString(ctx, ctx.Repo.Repository)
configurationModel, _ := devcontainer_service.UnmarshalDevcontainerConfigContent(configurationString)
imageName := configurationModel.Image
registry, namespace, repo, tag := devcontainer_service.ParseImageName(imageName)
2025-08-11 16:32:10 +08:00
log.Info("%v %v", repo, tag)
ctx.Data["RepositoryAddress"] = registry
ctx.Data["RepositoryUsername"] = namespace
ctx.Data["ImageName"] = "dev-" + ctx.Repo.Repository.Name + ":latest"
2025-09-18 17:19:14 +08:00
if cfg.Section("k8s").Key("ENABLE").Value() == "true" {
2025-08-16 18:31:14 +08:00
// 获取WebSSH服务端口
webTerminalURL, err := devcontainer_service.GetWebTerminalURL(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
if err != nil {
log.Info(err.Error())
ctx.Flash.Error(err.Error(), true)
} else {
ctx.Data["WebSSHUrl"] = webTerminalURL
}
} else {
2025-09-13 17:00:58 +08:00
webTerminalContainerName := cfg.Section("devcontainer").Key("WEB_TERMINAL_CONTAINER").Value()
if webTerminalContainerName == "" {
ctx.Flash.Error("webTerminal do not exist.", true)
} else {
2025-09-18 18:37:42 +08:00
rootPort, err := devcontainer_service.GetPortFromURL(cfg.Section("server").Key("ROOT_URL").Value())
if err != nil {
ctx.Flash.Error(err.Error(), true)
}
2025-09-13 17:00:58 +08:00
terminalParams := "user=" +
ctx.Doer.Name +
"&repo=" +
ctx.Repo.Repository.Name +
"&repoid=" +
strconv.FormatInt(ctx.Repo.Repository.ID, 10) +
"&userid=" +
strconv.FormatInt(ctx.Doer.ID, 10) +
"&domain=" +
cfg.Section("server").Key("DOMAIN").Value() +
"&port=" +
2025-09-18 18:37:42 +08:00
rootPort
2025-09-13 17:00:58 +08:00
port, err := devcontainer_service.GetMappedPort(ctx, webTerminalContainerName, "7681")
2025-09-18 17:19:14 +08:00
webTerminalURL, err := devcontainer_service.ReplacePortOfUrl(cfg.Section("server").Key("ROOT_URL").Value(), fmt.Sprintf("%d", port))
2025-09-13 17:00:58 +08:00
if err != nil {
log.Info(err.Error())
ctx.Flash.Error(err.Error(), true)
}
ctx.Data["WebSSHUrl"] = webTerminalURL + "?type=docker&" + terminalParams
2025-08-16 18:31:14 +08:00
}
2025-09-13 17:00:58 +08:00
2025-08-11 16:32:10 +08:00
}
}
// 3. 携带数据渲染页面,返回
ctx.Data["Title"] = ctx.Locale.Tr("repo.dev_container")
2025-08-16 18:31:14 +08:00
ctx.Data["PageIsDevContainer"] = true
2025-08-11 16:32:10 +08:00
ctx.Data["Repository"] = ctx.Repo.Repository
2025-08-16 18:31:14 +08:00
ctx.Data["CreateDevcontainerSettingUrl"] = "/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name + "/devcontainer/createConfiguration"
2025-08-11 16:32:10 +08:00
ctx.Data["EditDevcontainerConfigurationUrl"] = ctx.Repo.RepoLink + "/_edit/" + ctx.Repo.Repository.DefaultBranch + "/.devcontainer/devcontainer.json"
ctx.Data["TreeNames"] = []string{".devcontainer", "devcontainer.json"}
ctx.Data["TreePaths"] = []string{".devcontainer", ".devcontainer/devcontainer.json"}
2025-08-16 18:31:14 +08:00
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src"
2025-08-11 16:32:10 +08:00
ctx.Data["SaveMethods"] = []string{"Container", "DockerFile"}
ctx.Data["SaveMethod"] = "Container"
2025-08-16 18:31:14 +08:00
ctx.HTML(http.StatusOK, tplGetDevContainerDetails)
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
func GetDevContainerStatus(ctx *context.Context) {
// 设置 CORS 响应头
ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*")
ctx.Resp.Header().Set("Access-Control-Allow-Methods", "*")
ctx.Resp.Header().Set("Access-Control-Allow-Headers", "*")
var userID string
if ctx.Doer != nil {
userID = fmt.Sprintf("%d", ctx.Doer.ID)
2025-08-11 16:32:10 +08:00
} else {
2025-08-16 18:31:14 +08:00
query := ctx.Req.URL.Query()
userID = query.Get("user")
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
realTimeStatus, err := devcontainer_service.GetDevContainerStatus(ctx, userID, fmt.Sprintf("%d", ctx.Repo.Repository.ID))
2025-08-11 16:32:10 +08:00
if err != nil {
2025-08-16 18:31:14 +08:00
log.Info("%v\n", err)
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
ctx.JSON(http.StatusOK, map[string]string{"status": realTimeStatus})
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
func CreateDevContainerConfiguration(ctx *context.Context) {
hasDevContainerConfiguration, err := devcontainer_service.HasDevContainerConfiguration(ctx, ctx.Repo)
if err != nil {
log.Info(err.Error())
ctx.Flash.Error(err.Error(), true)
2025-08-11 16:32:10 +08:00
return
}
2025-08-16 18:31:14 +08:00
if hasDevContainerConfiguration {
ctx.Flash.Error("Already exist", true)
2025-08-11 16:32:10 +08:00
return
}
2025-08-16 18:31:14 +08:00
isAdmin, err := devcontainer_service.IsAdmin(ctx, ctx.Doer, ctx.Repo.Repository.ID)
2025-08-11 16:32:10 +08:00
if err != nil {
log.Info(err.Error())
2025-08-16 18:31:14 +08:00
ctx.Flash.Error(err.Error(), true)
2025-08-11 16:32:10 +08:00
return
}
2025-08-16 18:31:14 +08:00
if !isAdmin {
ctx.Flash.Error("permisson denied", true)
2025-08-11 16:32:10 +08:00
return
}
2025-08-16 18:31:14 +08:00
err = devcontainer_service.CreateDevcontainerConfiguration(ctx.Repo.Repository, ctx.Doer)
2025-08-11 16:32:10 +08:00
if err != nil {
2025-08-16 18:31:14 +08:00
ctx.Flash.Error(err.Error(), true)
2025-08-11 16:32:10 +08:00
return
}
2025-08-16 18:31:14 +08:00
ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer"))
}
func CreateDevContainer(ctx *context.Context) {
hasDevContainer, err := devcontainer_service.HasDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
2025-08-11 16:32:10 +08:00
if err != nil {
2025-08-16 18:31:14 +08:00
log.Info(err.Error())
ctx.Flash.Error(err.Error(), true)
2025-08-11 16:32:10 +08:00
return
}
2025-08-16 18:31:14 +08:00
if hasDevContainer {
ctx.Flash.Error("Already exist", true)
2025-08-11 16:32:10 +08:00
return
}
2025-08-21 21:31:51 +08:00
err = devcontainer_service.CreateDevcontainerAPIService(ctx, ctx.Repo.Repository, ctx.Doer, []string{}, true)
2025-08-11 16:32:10 +08:00
if err != nil {
2025-08-16 18:31:14 +08:00
ctx.Flash.Error(err.Error(), true)
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer"))
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
func DeleteDevContainer(ctx *context.Context) {
hasDevContainer, err := devcontainer_service.HasDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
if err != nil {
log.Info(err.Error())
ctx.Flash.Error(err.Error(), true)
return
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
if !hasDevContainer {
ctx.Flash.Error("Already Deleted.", true)
return
}
err = devcontainer_service.DeleteDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
2025-08-11 16:32:10 +08:00
if err != nil {
2025-08-16 18:31:14 +08:00
ctx.Flash.Error(err.Error(), true)
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer"))
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
func RestartDevContainer(ctx *context.Context) {
hasDevContainer, err := devcontainer_service.HasDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
2025-08-11 16:32:10 +08:00
if err != nil {
2025-08-16 18:31:14 +08:00
log.Info(err.Error())
ctx.Flash.Error(err.Error(), true)
return
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
if !hasDevContainer {
log.Info(err.Error())
ctx.Flash.Error("Already delete", true)
return
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
err = devcontainer_service.RestartDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
if err != nil {
ctx.Flash.Error(err.Error(), true)
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
ctx.JSON(http.StatusOK, "")
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
func StopDevContainer(ctx *context.Context) {
hasDevContainer, err := devcontainer_service.HasDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
2025-08-11 16:32:10 +08:00
if err != nil {
2025-08-16 18:31:14 +08:00
log.Info(err.Error())
ctx.Flash.Error(err.Error(), true)
2025-08-11 16:32:10 +08:00
return
}
2025-08-16 18:31:14 +08:00
if !hasDevContainer {
ctx.Flash.Error("Already delete", true)
return
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
err = devcontainer_service.StopDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
if err != nil {
ctx.Flash.Error(err.Error(), true)
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
ctx.JSON(http.StatusOK, "")
}
func UpdateDevContainer(ctx *context.Context) {
hasDevContainer, err := devcontainer_service.HasDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
2025-08-11 16:32:10 +08:00
if err != nil {
2025-08-16 18:31:14 +08:00
log.Info(err.Error())
ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()})
2025-08-11 16:32:10 +08:00
return
}
2025-08-16 18:31:14 +08:00
if !hasDevContainer {
ctx.JSON(http.StatusOK, map[string]string{"message": "Already delete"})
2025-08-11 16:32:10 +08:00
return
}
2025-08-16 18:31:14 +08:00
// 取得参数
body, _ := io.ReadAll(ctx.Req.Body)
var updateInfo devcontainer_service.UpdateInfo
err = json.Unmarshal(body, &updateInfo)
2025-08-11 16:32:10 +08:00
if err != nil {
2025-08-16 18:31:14 +08:00
ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()})
2025-08-11 16:32:10 +08:00
return
}
2025-08-16 18:31:14 +08:00
err = devcontainer_service.UpdateDevContainer(ctx, ctx.Doer, ctx.Repo.Repository, &updateInfo)
2025-08-11 16:32:10 +08:00
if err != nil {
2025-08-16 18:31:14 +08:00
ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()})
2025-08-11 16:32:10 +08:00
return
}
2025-08-16 18:31:14 +08:00
ctx.JSON(http.StatusOK, map[string]string{"redirect": ctx.Repo.RepoLink + "/devcontainer", "message": "成功"})
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
func GetTerminalCommand(ctx *context.Context) {
// 设置 CORS 响应头
ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*")
ctx.Resp.Header().Set("Access-Control-Allow-Methods", "*")
ctx.Resp.Header().Set("Access-Control-Allow-Headers", "*")
query := ctx.Req.URL.Query()
cmd, status, err := devcontainer_service.GetTerminalCommand(ctx, query.Get("user"), ctx.Repo.Repository)
2025-08-11 16:32:10 +08:00
if err != nil {
2025-08-16 18:31:14 +08:00
log.Info(err.Error())
status = "error"
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
ctx.JSON(http.StatusOK, map[string]string{"command": cmd, "status": status})
}
2025-08-11 16:32:10 +08:00
2025-08-16 18:31:14 +08:00
func GetDevContainerOutput(ctx *context.Context) {
// 设置 CORS 响应头
ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*")
ctx.Resp.Header().Set("Access-Control-Allow-Methods", "*")
ctx.Resp.Header().Set("Access-Control-Allow-Headers", "*")
output, err := devcontainer_service.GetDevContainerOutput(ctx, ctx.Doer, ctx.Repo.Repository)
if err != nil {
log.Info(err.Error())
2025-08-11 16:32:10 +08:00
}
2025-08-16 18:31:14 +08:00
ctx.JSON(http.StatusOK, output)
2025-08-11 16:32:10 +08:00
}