641 lines
19 KiB
Go
641 lines
19 KiB
Go
package devcontainer
|
|
|
|
import (
|
|
clictx "context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
devcontainer_models "code.gitea.io/gitea/models/devcontainer"
|
|
"code.gitea.io/gitea/modules/base"
|
|
docker_module "code.gitea.io/gitea/modules/docker"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/services/context"
|
|
devcontainer_service "code.gitea.io/gitea/services/devcontainer"
|
|
)
|
|
|
|
const (
|
|
tplGetRepoDevcontainerDetail base.TplName = "repo/devcontainer/details"
|
|
)
|
|
|
|
// 获取仓库 Dev Container 详细信息
|
|
// GET /{username}/{reponame}/dev-container
|
|
func GetRepoDevContainerDetails(ctx *context.Context) {
|
|
|
|
// 1. 查询当前 Repo 已有的 Dev Container 信息
|
|
opts := &devcontainer_service.RepoDevcontainerOptions{
|
|
Actor: ctx.Doer,
|
|
Repository: ctx.Repo.Repository,
|
|
}
|
|
|
|
ctx.Data["isAdmin"] = false
|
|
if ctx.Doer.IsAdmin {
|
|
ctx.Data["isAdmin"] = true
|
|
ctx.Data["canRead"] = true
|
|
} else {
|
|
canRead, _ := devcontainer_service.CanCreateDevcontainer(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID)
|
|
ctx.Data["canRead"] = canRead
|
|
isAdmin, _ := devcontainer_service.IsOwner(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID)
|
|
ctx.Data["isAdmin"] = isAdmin
|
|
}
|
|
|
|
//ctx.Repo.RepoLink == ctx.Repo.Repository.Link()
|
|
devContainerMetadata, err := devcontainer_service.GetRepoDevcontainerDetails(ctx, opts)
|
|
hasDevContainer := err == nil && devContainerMetadata.DevContainerId > 0
|
|
ctx.Data["HasDevContainer"] = hasDevContainer
|
|
ctx.Data["HasDevContainerSetting"] = false
|
|
|
|
if hasDevContainer {
|
|
ctx.Data["DevContainer"] = devContainerMetadata
|
|
}
|
|
|
|
// 2. 检查当前仓库的当前分支是否存在有效的 /.devcontainer/devcontainer.json
|
|
isValidRepoDevcontainerJson := isValidRepoDevcontainerJsonFile(ctx)
|
|
hasDockerfile := isValidRepoDevcontainerDockerfile(ctx)
|
|
if !hasDevContainer && !isValidRepoDevcontainerJson {
|
|
ctx.Flash.Error(ctx.Tr("repo.dev_container_invalid_config_prompt"), true)
|
|
}
|
|
ctx.Data["HasDockerfile"] = false
|
|
if hasDockerfile {
|
|
ctx.Data["HasDockerfile"] = true
|
|
}
|
|
// 从devcontainer.json文件提取image字段解析成仓库地址、命名空间、镜像名
|
|
devcontainerJson, err := devcontainer_service.GetDevcontainerJsonModel(ctx, ctx.Repo.Repository)
|
|
if err == nil {
|
|
imageName := devcontainerJson.Image
|
|
registry, namespace, repo, tag := ParseImageName(imageName)
|
|
log.Info("%v %v", repo, tag)
|
|
ctx.Data["RepositoryAddress"] = registry
|
|
ctx.Data["RepositoryUsername"] = namespace
|
|
ctx.Data["ImageName"] = "dev-" + ctx.Repo.Repository.Name + ":latest"
|
|
}
|
|
|
|
ctx.Data["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)
|
|
if setting.Devcontainer.Agent == setting.KUBERNETES || setting.Devcontainer.Agent == "k8s" {
|
|
// 获取WebSSH服务端口
|
|
webTerminalURL, err := devcontainer_service.GetWebTerminalURL(ctx, devContainerMetadata.DevContainerName)
|
|
if err == nil {
|
|
ctx.Data["WebSSHUrl"] = webTerminalURL
|
|
}
|
|
|
|
} else {
|
|
ctx.Data["WebSSHUrl"] = "http://localhost:3000/assets/terminal/index.html?type=docker&" + fmt.Sprintf("%v", ctx.Data["TerminalParams"])
|
|
}
|
|
|
|
terminalURL, err := devcontainer_service.Get_IDE_TerminalURL(ctx, &devContainerMetadata)
|
|
if err == nil {
|
|
ctx.Data["VSCodeUrl"] = "vscode" + terminalURL
|
|
ctx.Data["CursorUrl"] = "cursor" + terminalURL
|
|
ctx.Data["WindsurfUrl"] = "windsurf" + terminalURL
|
|
}
|
|
|
|
//存在devcontainer.json读取信息展示
|
|
if isValidRepoDevcontainerJson {
|
|
fileContent, err := devcontainer_service.GetDevcontainerJsonString(ctx, ctx.Repo.Repository)
|
|
if err == nil {
|
|
ctx.Data["FileContent"] = fileContent
|
|
}
|
|
}
|
|
|
|
// 3. 携带数据渲染页面,返回
|
|
ctx.Data["Title"] = ctx.Locale.Tr("repo.dev_container")
|
|
ctx.Data["PageIsRepoDevcontainerDetails"] = true
|
|
ctx.Data["HasValidDevContainerJSON"] = isValidRepoDevcontainerJson
|
|
ctx.Data["Repository"] = ctx.Repo.Repository
|
|
ctx.Data["ContextUser"] = ctx.Doer
|
|
ctx.Data["CreateDevcontainerSettingUrl"] = "/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name + "/dev-container/createConfiguration"
|
|
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"}
|
|
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
|
ctx.Data["SaveMethods"] = []string{"Container", "DockerFile"}
|
|
ctx.Data["SaveMethod"] = "Container"
|
|
ctx.HTML(http.StatusOK, tplGetRepoDevcontainerDetail)
|
|
}
|
|
|
|
func CreateRepoDevContainerConfiguration(ctx *context.Context) {
|
|
if !ctx.Doer.IsAdmin {
|
|
ctx.Flash.Error("permisson denied", true)
|
|
return
|
|
}
|
|
devcontainer_service.CreateDevcontainerJSON(ctx, ctx.Repo.Repository, ctx.Doer)
|
|
ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/dev-container"))
|
|
}
|
|
func ParseImageName(imageName string) (registry, namespace, repo, tag string) {
|
|
|
|
// 分离仓库地址和命名空间
|
|
parts := strings.Split(imageName, "/")
|
|
if len(parts) == 3 {
|
|
registry = parts[0]
|
|
namespace = parts[1]
|
|
repo = parts[2]
|
|
} else if len(parts) == 2 {
|
|
registry = parts[0]
|
|
repo = parts[1]
|
|
} else {
|
|
repo = imageName
|
|
}
|
|
// 分离标签
|
|
parts = strings.Split(repo, ":")
|
|
if len(parts) > 1 {
|
|
tag = parts[1]
|
|
repo = parts[0]
|
|
} else {
|
|
tag = "latest"
|
|
}
|
|
if registry == "" {
|
|
registry = "docker.io"
|
|
}
|
|
return registry, namespace, repo, tag
|
|
}
|
|
|
|
// 创建仓库 Dev Container
|
|
func CreateRepoDevContainer(ctx *context.Context) {
|
|
if !isUserDevcontainerAlreadyInRepository(ctx) {
|
|
opts := &devcontainer_service.CreateRepoDevcontainerOptions{
|
|
Actor: ctx.Doer,
|
|
Repository: ctx.Repo.Repository,
|
|
}
|
|
devcontainer_service.CreateRepoDevcontainer(ctx, opts)
|
|
}
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/dev-container")
|
|
}
|
|
|
|
// 辅助判断当前仓库的当前分支是否存在有效的 /.devcontainer/devcontainer.json
|
|
func isValidRepoDevcontainerJsonFile(ctx *context.Context) bool {
|
|
|
|
// 1. 仓库非空,且非 Archived 状态
|
|
if ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsArchived {
|
|
return false
|
|
}
|
|
|
|
// 2. 当前分支的目录 .devcontainer 下存在 devcontainer.json 文件
|
|
fileDevcontainerJsonExists, err := ctx.Repo.FileExists(".devcontainer/devcontainer.json", ctx.Repo.BranchName)
|
|
if err != nil || !fileDevcontainerJsonExists {
|
|
return false
|
|
}
|
|
|
|
// 3. TODO: DevContainer 格式正确
|
|
return true
|
|
}
|
|
|
|
// 辅助判断当前仓库的当前分支是否存在有效的 /.devcontainer/Dockerfile
|
|
func isValidRepoDevcontainerDockerfile(ctx *context.Context) bool {
|
|
|
|
// 1. 仓库非空,且非 Archived 状态
|
|
if ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsArchived {
|
|
return false
|
|
}
|
|
|
|
// 2. 当前分支的目录 .devcontainer 下存在 devcontainer.json 文件
|
|
dockerfilePath, err := devcontainer_service.GetDockerfilePath(ctx, ctx.Repo.Repository)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
dockerfileExists, err := ctx.Repo.FileExists(".devcontainer/"+dockerfilePath, ctx.Repo.BranchName)
|
|
if err != nil || !dockerfileExists {
|
|
return false
|
|
}
|
|
|
|
// 3. TODO: DevContainer 格式正确
|
|
return true
|
|
}
|
|
|
|
// 辅助判断当前用户在当前仓库是否已有 Dev Container
|
|
func isUserDevcontainerAlreadyInRepository(ctx *context.Context) bool {
|
|
|
|
opts := &devcontainer_service.RepoDevcontainerOptions{
|
|
Actor: ctx.Doer,
|
|
Repository: ctx.Repo.Repository,
|
|
}
|
|
|
|
devcontainerDetails, _ := devcontainer_service.GetRepoDevcontainerDetails(ctx, opts)
|
|
return devcontainerDetails.DevContainerId > 0
|
|
}
|
|
|
|
func UpdateRepoDevContainerForCurrentActor(ctx *context.Context) {
|
|
if !ctx.Doer.IsAdmin {
|
|
ctx.Flash.Error("permisson denied", true)
|
|
return
|
|
}
|
|
opt := &devcontainer_service.RepoDevcontainerOptions{
|
|
Actor: ctx.Doer,
|
|
Repository: ctx.Repo.Repository,
|
|
}
|
|
devContainerMetadata, _ := devcontainer_service.GetRepoDevcontainerDetails(ctx, opt)
|
|
|
|
// 取得参数
|
|
body, _ := io.ReadAll(ctx.Req.Body)
|
|
log.Info(string(body))
|
|
var updateInfo devcontainer_service.UpdateInfo
|
|
err := json.Unmarshal(body, &updateInfo)
|
|
// 保存容器功能使用弹窗显示错误信息
|
|
if err != nil {
|
|
log.Info("保存容器参数反序列化失败:", err)
|
|
ctx.JSON(400, map[string]string{"message": "输入错误"})
|
|
return
|
|
}
|
|
opts := &devcontainer_service.UpdateDevcontainerOptions{
|
|
ImageName: updateInfo.ImageName,
|
|
PassWord: updateInfo.PassWord,
|
|
RepositoryAddress: updateInfo.RepositoryAddress,
|
|
RepositoryUsername: updateInfo.RepositoryUsername,
|
|
DevContainerName: devContainerMetadata.DevContainerName,
|
|
Actor: ctx.Doer,
|
|
Repository: ctx.Repo.Repository,
|
|
SaveMethod: updateInfo.SaveMethod,
|
|
}
|
|
err = devcontainer_service.UpdateDevcontainerAPIService(ctx, opts)
|
|
if err != nil {
|
|
ctx.JSON(500, map[string]string{"message": err.Error()})
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, map[string]string{"redirect": ctx.Repo.RepoLink + "/dev-container", "message": "成功"})
|
|
}
|
|
|
|
// 删除仓库 当前用户 Dev Container
|
|
func DeleteRepoDevContainerForCurrentActor(ctx *context.Context) {
|
|
if isUserDevcontainerAlreadyInRepository(ctx) {
|
|
opts := &devcontainer_service.RepoDevcontainerOptions{
|
|
Actor: ctx.Doer,
|
|
Repository: ctx.Repo.Repository,
|
|
}
|
|
err := devcontainer_service.DeleteRepoDevcontainer(ctx, opts)
|
|
if err != nil {
|
|
log.Warn("failed to delete devContainer with option{%v}: %v", opts, err.Error())
|
|
ctx.Flash.Error(ctx.Tr("repo.dev_container_control.deletion_failed_for_user", ctx.Doer.Name))
|
|
} else {
|
|
ctx.Flash.Success(ctx.Tr("repo.dev_container_control.deletion_success_for_user", ctx.Doer.Name))
|
|
}
|
|
}
|
|
ctx.JSONRedirect(ctx.Repo.RepoLink + "/dev-container")
|
|
}
|
|
|
|
type OutputResponse struct {
|
|
CurrentJob struct {
|
|
IP string `json:"ip"`
|
|
Port string `json:"port"`
|
|
Title string `json:"title"`
|
|
Detail string `json:"detail"`
|
|
Steps []*ViewJobStep `json:"steps"`
|
|
} `json:"currentDevcontainer"`
|
|
}
|
|
type ViewJobStep struct {
|
|
Summary string `json:"summary"`
|
|
Duration string `json:"duration"`
|
|
Status string `json:"status"`
|
|
Logs []ViewStepLogLine `json:"logs"`
|
|
}
|
|
|
|
type ViewStepLogLine struct {
|
|
Index int64 `json:"index"`
|
|
Message string `json:"message"`
|
|
Timestamp float64 `json:"timestamp"`
|
|
}
|
|
|
|
func GetContainerOutput(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()
|
|
var paramInfo = ParamInfo{
|
|
RepoID: query.Get("repo"),
|
|
UserID: query.Get("user"),
|
|
}
|
|
var devContainerOutput []devcontainer_models.DevcontainerOutput
|
|
dbEngine := db.GetEngine(*ctx)
|
|
|
|
var status string
|
|
_, err := db.GetEngine(ctx).
|
|
Table("devcontainer").
|
|
Select("devcontainer_status").
|
|
Where("user_id = ? AND repo_id = ?", paramInfo.UserID, paramInfo.RepoID).
|
|
Get(&status)
|
|
if err != nil {
|
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
var containerName string
|
|
_, err = db.GetEngine(ctx).
|
|
Table("devcontainer").
|
|
Select("name").
|
|
Where("user_id = ? AND repo_id = ?", paramInfo.UserID, paramInfo.RepoID).
|
|
Get(&containerName)
|
|
if err != nil {
|
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
err = dbEngine.Table("devcontainer_output").
|
|
Where("user_id = ? AND repo_id = ?", paramInfo.UserID, paramInfo.RepoID).
|
|
Find(&devContainerOutput)
|
|
|
|
if err != nil {
|
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
if len(devContainerOutput) > 0 {
|
|
resp := &OutputResponse{}
|
|
resp.CurrentJob.Title = ctx.Repo.Repository.Name + " Devcontainer Info"
|
|
resp.CurrentJob.Detail = status
|
|
if status == "4" {
|
|
// 获取WebSSH服务端口
|
|
webTerminalURL, err := devcontainer_service.GetWebTerminalURL(ctx, containerName)
|
|
if err == nil {
|
|
log.Info("URL解析失败: %v", err)
|
|
}
|
|
// 解析URL
|
|
u, err := url.Parse(webTerminalURL)
|
|
if err != nil {
|
|
log.Info("URL解析失败: %v", err)
|
|
}
|
|
// 分离主机和端口
|
|
terminalHost, terminalPort, err := net.SplitHostPort(u.Host)
|
|
resp.CurrentJob.IP = terminalHost
|
|
resp.CurrentJob.Port = terminalPort
|
|
if err != nil {
|
|
log.Info("URL解析失败: %v", err)
|
|
}
|
|
}
|
|
for _, item := range devContainerOutput {
|
|
logLines := []ViewStepLogLine{}
|
|
logLines = append(logLines, ViewStepLogLine{
|
|
Index: 1,
|
|
Message: item.Output,
|
|
})
|
|
resp.CurrentJob.Steps = append(resp.CurrentJob.Steps, &ViewJobStep{
|
|
Summary: item.Command,
|
|
Status: item.Status,
|
|
Logs: logLines,
|
|
})
|
|
|
|
}
|
|
ctx.JSON(http.StatusOK, resp)
|
|
return
|
|
} else {
|
|
resp := &OutputResponse{}
|
|
ctx.JSON(http.StatusOK, resp)
|
|
}
|
|
|
|
}
|
|
func RestartContainer(ctx *context.Context) {
|
|
opt := &devcontainer_service.RepoDevcontainerOptions{
|
|
Actor: ctx.Doer,
|
|
Repository: ctx.Repo.Repository,
|
|
}
|
|
devContainerMetadata, _ := devcontainer_service.GetRepoDevcontainerDetails(ctx, opt)
|
|
|
|
err := devcontainer_service.RestartDevcontainer(*ctx, &devContainerMetadata)
|
|
if err != nil {
|
|
ctx.Flash.Error("fail to restart container")
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, map[string]string{})
|
|
}
|
|
func StopContainer(ctx *context.Context) {
|
|
opt := &devcontainer_service.RepoDevcontainerOptions{
|
|
Actor: ctx.Doer,
|
|
Repository: ctx.Repo.Repository,
|
|
}
|
|
devContainerMetadata, _ := devcontainer_service.GetRepoDevcontainerDetails(ctx, opt)
|
|
|
|
err := devcontainer_service.StopDevcontainer(ctx, &devContainerMetadata)
|
|
if err != nil {
|
|
ctx.Flash.Error("fail to stop container")
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, map[string]string{})
|
|
}
|
|
|
|
type ParamInfo struct {
|
|
RepoID string
|
|
UserID string
|
|
}
|
|
|
|
func GetCommand(ctx *context.Context) {
|
|
query := ctx.Req.URL.Query()
|
|
var commandInfo = ParamInfo{
|
|
RepoID: query.Get("repo"),
|
|
UserID: query.Get("user"),
|
|
}
|
|
dbEngine := db.GetEngine(*ctx)
|
|
var status uint16
|
|
var containerName string
|
|
var containerID uint16
|
|
var work_dir string
|
|
var cmd string
|
|
var flag uint16
|
|
//当前状态
|
|
_, err := dbEngine.
|
|
Table("devcontainer").
|
|
Select("devcontainer_status, id, name, devcontainer_work_dir").
|
|
Where("user_id = ? AND repo_id = ?", commandInfo.UserID, commandInfo.RepoID).
|
|
Get(&status, &containerID, &containerName, &work_dir)
|
|
if err != nil {
|
|
log.Info("Error: %v\n", err)
|
|
}
|
|
flag = status
|
|
|
|
switch status {
|
|
case 0:
|
|
if containerID > 0 {
|
|
flag = 1
|
|
}
|
|
case 1:
|
|
devContainerJson, err := devcontainer_service.GetDevcontainerJsonModel(ctx, ctx.Repo.Repository)
|
|
if err != nil {
|
|
log.Info("Error : %v\n", err)
|
|
break
|
|
}
|
|
buffer, err := docker_module.ImageExists(devContainerJson.Image)
|
|
if err != nil {
|
|
log.Info("Error : %v\n", err)
|
|
break
|
|
}
|
|
if buffer {
|
|
flag = 2
|
|
}
|
|
case 2:
|
|
// 创建docker client
|
|
cliCtx := clictx.Background()
|
|
cli, err := docker_module.CreateDockerClient(&cliCtx)
|
|
if err != nil {
|
|
break
|
|
}
|
|
defer cli.Close()
|
|
containerID, err := docker_module.GetContainerID(cli, containerName)
|
|
containerStatus, err := docker_module.GetContainerStatus(cli, containerID)
|
|
if containerStatus == "running" {
|
|
flag = 3
|
|
}
|
|
case 3:
|
|
// 创建docker client
|
|
cliCtx := clictx.Background()
|
|
cli, err := docker_module.CreateDockerClient(&cliCtx)
|
|
if err != nil {
|
|
break
|
|
}
|
|
defer cli.Close()
|
|
containerID, err := docker_module.GetContainerID(cli, containerName)
|
|
dirStatus, err := docker_module.CheckDirExists(cli, containerID, work_dir)
|
|
if dirStatus {
|
|
flag = 4
|
|
}
|
|
case 4:
|
|
cmd = "docker exec -it --workdir " + work_dir + " " + containerName + " /bin/bash -c \"echo 'Successfully connected to the container';bash\"\n"
|
|
default:
|
|
log.Info("other status")
|
|
}
|
|
|
|
if flag != status {
|
|
//下一条指令
|
|
_, err = dbEngine.Table("devcontainer_output").
|
|
Select("command").
|
|
Where("user_id = ? AND repo_id = ? AND list_id = ?", commandInfo.UserID, commandInfo.RepoID, flag).
|
|
Get(&cmd)
|
|
if err != nil {
|
|
log.Info("Error : %v\n", err)
|
|
}
|
|
_, err = dbEngine.Table("devcontainer").
|
|
Where("user_id = ? AND repo_id = ? ", commandInfo.UserID, commandInfo.RepoID).
|
|
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: flag})
|
|
if err != nil {
|
|
log.Info("err %v", err)
|
|
}
|
|
}
|
|
// 设置 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", "*")
|
|
ctx.JSON(http.StatusOK, map[string]string{"command": cmd, "status": fmt.Sprint(flag)})
|
|
}
|
|
func GetTerminalToken(ctx *context.Context) {
|
|
query := ctx.Req.URL.Query()
|
|
var containerName string
|
|
_, err := db.GetEngine(ctx).
|
|
Table("devcontainer").
|
|
Select("name").
|
|
Where("user_id = ? AND repo_id = ?", query.Get("user"), query.Get("repo")).
|
|
Get(&containerName)
|
|
if err != nil {
|
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if containerName == "" {
|
|
ctx.Error(404, "Service not found")
|
|
}
|
|
// 获取WebSSH服务端口
|
|
webTerminalURL, err := devcontainer_service.GetWebTerminalURL(ctx, containerName)
|
|
if err == nil {
|
|
log.Info("URL解析失败: %v", err)
|
|
}
|
|
// 解析URL
|
|
u, err := url.Parse(webTerminalURL)
|
|
if err != nil {
|
|
log.Info("URL解析失败: %v", err)
|
|
return
|
|
}
|
|
// 分离主机和端口
|
|
terminalHost, terminalPort, err := net.SplitHostPort(u.Host)
|
|
|
|
if err != nil {
|
|
|
|
log.Info("URL解析失败: %v", err)
|
|
return
|
|
}
|
|
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Get("http://" + terminalHost + ":" + terminalPort + "/token")
|
|
if err != nil {
|
|
log.Error("Failed to connect terminal: %v", err)
|
|
ctx.Error(http.StatusInternalServerError, "Failed to connect terminal")
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Error("Failed to read response body: %v", err)
|
|
ctx.Error(http.StatusInternalServerError, "Failed to read response body")
|
|
return
|
|
}
|
|
ctx.Resp.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
|
|
ctx.Status(resp.StatusCode)
|
|
_, _ = ctx.Write(body)
|
|
|
|
}
|
|
func GetContainerStatus(ctx *context.Context) {
|
|
var status string
|
|
var id int
|
|
var containerName string
|
|
var flag uint16
|
|
|
|
log.Info("GetContainerStatus %v 666 %v", ctx.Doer.ID, ctx.Repo.Repository.ID)
|
|
_, err := db.GetEngine(ctx).
|
|
Table("devcontainer").
|
|
Select("devcontainer_status, id, name").
|
|
Where("user_id = ? AND repo_id = ?", ctx.Doer.ID, ctx.Repo.Repository.ID).
|
|
Get(&status, &id, &containerName)
|
|
if err != nil {
|
|
log.Info("Error: %v\n", err)
|
|
}
|
|
if id == 0 {
|
|
status = "-"
|
|
}
|
|
|
|
switch status {
|
|
case "5":
|
|
// 创建docker client
|
|
cliCtx := clictx.Background()
|
|
cli, err := docker_module.CreateDockerClient(&cliCtx)
|
|
if err != nil {
|
|
break
|
|
}
|
|
defer cli.Close()
|
|
containerID, err := docker_module.GetContainerID(cli, containerName)
|
|
containerStatus, err := docker_module.GetContainerStatus(cli, containerID)
|
|
if containerStatus == "running" {
|
|
flag = 4
|
|
}
|
|
|
|
case "6":
|
|
// 创建docker client
|
|
cliCtx := clictx.Background()
|
|
cli, err := docker_module.CreateDockerClient(&cliCtx)
|
|
if err != nil {
|
|
break
|
|
}
|
|
defer cli.Close()
|
|
containerID, err := docker_module.GetContainerID(cli, containerName)
|
|
containerStatus, err := docker_module.GetContainerStatus(cli, containerID)
|
|
if containerStatus == "exited" {
|
|
flag = 8
|
|
}
|
|
|
|
default:
|
|
log.Info("other status")
|
|
}
|
|
if fmt.Sprintf("%d", flag) != status {
|
|
_, err = db.GetEngine(ctx).Table("devcontainer").
|
|
Where("user_id = ? AND repo_id = ? ", ctx.Doer.ID, ctx.Repo.Repository.ID).
|
|
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: flag})
|
|
if err != nil {
|
|
log.Info("err %v", err)
|
|
}
|
|
}
|
|
ctx.JSON(http.StatusOK, map[string]string{"status": status})
|
|
}
|