592 lines
18 KiB
Go
592 lines
18 KiB
Go
package devcontainer
|
||
|
||
import (
|
||
"archive/tar"
|
||
"bytes"
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"os/exec"
|
||
"regexp"
|
||
"strings"
|
||
"time"
|
||
|
||
"code.gitea.io/gitea/models/db"
|
||
devcontainer_models "code.gitea.io/gitea/models/devcontainer"
|
||
"code.gitea.io/gitea/models/repo"
|
||
"code.gitea.io/gitea/models/user"
|
||
docker_module "code.gitea.io/gitea/modules/docker"
|
||
"code.gitea.io/gitea/modules/log"
|
||
"code.gitea.io/gitea/modules/setting"
|
||
"github.com/docker/docker/api/types"
|
||
"github.com/docker/docker/api/types/container"
|
||
"github.com/docker/docker/errdefs"
|
||
"github.com/docker/go-connections/nat"
|
||
)
|
||
|
||
func GetDevContainerStatusFromDocker(ctx context.Context, containerName string) (string, error) {
|
||
// 创建docker client
|
||
cli, err := docker_module.CreateDockerClient(ctx)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
defer cli.Close()
|
||
containerID, err := docker_module.GetContainerID(cli, containerName)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
containerStatus, err := docker_module.GetContainerStatus(cli, containerID)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return containerStatus, nil
|
||
}
|
||
func CreateDevContainerByDockerAPI(ctx context.Context, newDevcontainer *devcontainer_models.Devcontainer, imageName string, repo *repo.Repository, publicKeyList []string) error {
|
||
dbEngine := db.GetEngine(ctx)
|
||
configurationString, err := GetDevcontainerConfigurationString(ctx, repo)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
dockerSocket, err := docker_module.GetDockerSocketPath()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
_, err = dbEngine.Table("devcontainer").
|
||
Where("user_id = ? AND repo_id = ? ", newDevcontainer.UserId, newDevcontainer.RepoId).
|
||
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 1})
|
||
if err != nil {
|
||
log.Info("err %v", err)
|
||
}
|
||
cli, err := docker_module.CreateDockerClient(ctx)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if configurationModel.Build == nil || configurationModel.Build.Dockerfile == "" {
|
||
script := "docker " + "-H " + dockerSocket + " pull " + configurationModel.Image
|
||
cmd := exec.Command("sh", "-c", script)
|
||
err = cmd.Start()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
_, err = dbEngine.Table("devcontainer").
|
||
Where("user_id = ? AND repo_id = ? ", newDevcontainer.UserId, newDevcontainer.RepoId).
|
||
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 2})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
docker_module.CreateAndStartContainer(ctx, cli, imageName,
|
||
[]string{
|
||
"sh",
|
||
"-c",
|
||
"tail -f /dev/null;",
|
||
},
|
||
nil,
|
||
nil,
|
||
nat.PortSet{
|
||
nat.Port("22/tcp"): {},
|
||
},
|
||
newDevcontainer.Name)
|
||
_, err = dbEngine.Table("devcontainer").
|
||
Where("user_id = ? AND repo_id = ? ", newDevcontainer.UserId, newDevcontainer.RepoId).
|
||
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 3})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
log.Info("ExecCommandInContainerExecCommandInContainerExecCommandInContainerExecCommandInContainerExecCommandInContainer")
|
||
output, err := docker_module.ExecCommandInContainer(ctx, cli, newDevcontainer.Name,
|
||
`echo "`+newDevcontainer.DevcontainerHost+` host.docker.internal" | tee -a /etc/hosts;apt update;apt install -y git ;git clone `+strings.TrimSuffix(setting.AppURL, "/")+repo.Link()+" "+newDevcontainer.DevcontainerWorkDir+"/"+repo.Name+`; apt install -y ssh;echo "PubkeyAuthentication yes `+"\n"+`PermitRootLogin yes `+"\n"+`" | tee -a /etc/ssh/sshd_config;rm -f /etc/ssh/ssh_host_*; ssh-keygen -A; service ssh restart;mkdir -p ~/.ssh;chmod 700 ~/.ssh;echo "`+strings.Join(publicKeyList, "\n")+`" > ~/.ssh/authorized_keys;chmod 600 ~/.ssh/authorized_keys;`,
|
||
)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
log.Info(output)
|
||
|
||
_, err = dbEngine.Table("devcontainer").
|
||
Where("user_id = ? AND repo_id = ? ", newDevcontainer.UserId, newDevcontainer.RepoId).
|
||
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 4})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func CreateDevContainerByDockerCommand(ctx context.Context, newDevcontainer *devcontainer_models.Devcontainer, repo *repo.Repository, publicKeyList []string) (string, error) {
|
||
|
||
dbEngine := db.GetEngine(ctx)
|
||
configurationString, err := GetDevcontainerConfigurationString(ctx, repo)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
var imageName = configurationModel.Image
|
||
dockerSocket, err := docker_module.GetDockerSocketPath()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
if configurationModel.Build != nil && configurationModel.Build.Dockerfile != "" {
|
||
dockerfileContent, err := GetFileContentByPath(ctx, repo, ".devcontainer/"+configurationModel.Build.Dockerfile)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
// 创建构建上下文(包含Dockerfile的tar包)
|
||
var buf bytes.Buffer
|
||
tw := tar.NewWriter(&buf)
|
||
defer tw.Close()
|
||
// 添加Dockerfile到tar包
|
||
dockerfile := "Dockerfile"
|
||
content := []byte(dockerfileContent)
|
||
header := &tar.Header{
|
||
Name: dockerfile,
|
||
Size: int64(len(content)),
|
||
Mode: 0644,
|
||
}
|
||
if err := tw.WriteHeader(header); err != nil {
|
||
return "", err
|
||
}
|
||
if _, err := tw.Write(content); err != nil {
|
||
return "", err
|
||
}
|
||
// 执行镜像构建
|
||
imageName = fmt.Sprintf("%d", newDevcontainer.UserId) + "-" + fmt.Sprintf("%d", newDevcontainer.RepoId) + "-dockerfile"
|
||
buildOptions := types.ImageBuildOptions{
|
||
Tags: []string{imageName}, // 镜像标签
|
||
}
|
||
cli, err := docker_module.CreateDockerClient(ctx)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
buildResponse, err := cli.ImageBuild(
|
||
context.Background(),
|
||
&buf,
|
||
buildOptions,
|
||
)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
output, err := io.ReadAll(buildResponse.Body)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
log.Info(string(output))
|
||
|
||
}
|
||
|
||
// 拉取镜像的命令
|
||
if _, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
||
Output: "",
|
||
ListId: 1,
|
||
Status: "waitting",
|
||
UserId: newDevcontainer.UserId,
|
||
RepoId: newDevcontainer.RepoId,
|
||
Command: "docker " + "-H " + dockerSocket + " pull " + imageName + "\n",
|
||
DevcontainerId: newDevcontainer.Id,
|
||
}); err != nil {
|
||
log.Info("Failed to insert record: %v", err)
|
||
return imageName, err
|
||
}
|
||
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
||
if err != nil {
|
||
return imageName, err
|
||
}
|
||
var startCommand string = `docker -H ` + dockerSocket + ` run -d --name ` + newDevcontainer.Name
|
||
|
||
// 将每个端口转换为 "-p <port>" 格式
|
||
var portFlags string = " -p 22 "
|
||
|
||
exposedPorts := configurationModel.ExtractContainerPorts()
|
||
for _, port := range exposedPorts {
|
||
portFlags = portFlags + fmt.Sprintf(" -p %d ", port)
|
||
}
|
||
startCommand += portFlags
|
||
var envFlags string = ` -e RepoLink="` + strings.TrimSuffix(cfg.Section("server").Key("ROOT_URL").Value(), `/`) + repo.Link() + `" ` +
|
||
` -e WorkSpace="` + newDevcontainer.DevcontainerWorkDir + `/` + repo.Name + `"` +
|
||
` -e PublicKeyList="` + strings.Join(publicKeyList, "\n") + `" `
|
||
// 遍历 ContainerEnv 映射中的每个环境变量
|
||
for name, value := range configurationModel.ContainerEnv {
|
||
// 将每个环境变量转换为 "-e name=value" 格式
|
||
envFlags = envFlags + fmt.Sprintf(" -e %s=\"%s\" ", name, value)
|
||
}
|
||
startCommand += envFlags
|
||
if configurationModel.Init {
|
||
startCommand += " --init "
|
||
}
|
||
if configurationModel.Privileged {
|
||
startCommand += " --privileged "
|
||
}
|
||
|
||
var capAddFlags string
|
||
// 遍历 CapAdd 列表中的每个能力
|
||
for _, capability := range configurationModel.CapAdd {
|
||
// 将每个能力转换为 --cap-add=capability 格式
|
||
capAddFlags = capAddFlags + fmt.Sprintf(" --cap-add %s ", capability)
|
||
}
|
||
startCommand += capAddFlags
|
||
|
||
var securityOptFlags string
|
||
// 遍历 SecurityOpt 列表中的每个安全选项
|
||
for _, option := range configurationModel.SecurityOpt {
|
||
// 将每个选项转换为 --security-opt=option 格式
|
||
securityOptFlags = securityOptFlags + fmt.Sprintf(" --security-opt %s ", option)
|
||
}
|
||
startCommand += securityOptFlags
|
||
startCommand += " " + strings.Join(configurationModel.ExtractMountFlags(), " ") + " "
|
||
if configurationModel.WorkspaceFolder != "" {
|
||
startCommand += fmt.Sprintf(" -w %s ", configurationModel.WorkspaceFolder)
|
||
}
|
||
startCommand += " " + strings.Join(configurationModel.RunArgs, " ") + " "
|
||
//创建并运行容器的命令
|
||
if _, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
||
Output: "",
|
||
Status: "waitting",
|
||
UserId: newDevcontainer.UserId,
|
||
RepoId: newDevcontainer.RepoId,
|
||
Command: startCommand + imageName + ` sh -c "tail -f /dev/null"` + "\n",
|
||
ListId: 2,
|
||
DevcontainerId: newDevcontainer.Id,
|
||
}); err != nil {
|
||
log.Info("Failed to insert record: %v", err)
|
||
return imageName, err
|
||
}
|
||
//安装基本工具的命令
|
||
onCreateCommand := strings.TrimSpace(strings.Join(configurationModel.ParseCommand(configurationModel.OnCreateCommand), ";"))
|
||
if !strings.HasSuffix(onCreateCommand, ";") {
|
||
onCreateCommand += ";"
|
||
}
|
||
if onCreateCommand == ";" {
|
||
onCreateCommand = ""
|
||
}
|
||
if _, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
||
Output: "",
|
||
Status: "waitting",
|
||
UserId: newDevcontainer.UserId,
|
||
RepoId: newDevcontainer.RepoId,
|
||
Command: `docker -H ` + dockerSocket + ` exec ` + newDevcontainer.Name + ` /home/webTerminal.sh start; ` +
|
||
onCreateCommand + "\n",
|
||
ListId: 3,
|
||
DevcontainerId: newDevcontainer.Id,
|
||
}); err != nil {
|
||
log.Info("Failed to insert record: %v", err)
|
||
return imageName, err
|
||
}
|
||
//连接容器的命令
|
||
if _, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
||
Output: "",
|
||
Status: "waitting",
|
||
UserId: newDevcontainer.UserId,
|
||
RepoId: newDevcontainer.RepoId,
|
||
Command: `docker -H ` + dockerSocket + ` exec -it --workdir ` + newDevcontainer.DevcontainerWorkDir + ` ` + newDevcontainer.Name + ` sh -c "echo 'Successfully connected to the container';bash"` + "\n",
|
||
ListId: 4,
|
||
DevcontainerId: newDevcontainer.Id,
|
||
}); err != nil {
|
||
log.Info("Failed to insert record: %v", err)
|
||
return imageName, err
|
||
}
|
||
return imageName, nil
|
||
}
|
||
func IsContainerNotFound(ctx context.Context, containerName string) (bool, error) {
|
||
cli, err := docker_module.CreateDockerClient(ctx)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
defer cli.Close()
|
||
containerID, err := docker_module.GetContainerID(cli, containerName)
|
||
if err != nil {
|
||
// 检查是否为 "未找到" 错误
|
||
if errdefs.IsNotFound(err) {
|
||
return true, nil
|
||
}
|
||
return false, err
|
||
}
|
||
_, err = cli.ContainerInspect(ctx, containerID)
|
||
if err != nil {
|
||
|
||
// 检查是否为 "未找到" 错误
|
||
if docker_module.IsContainerNotFound(err) {
|
||
return true, nil
|
||
}
|
||
// 其他类型的错误
|
||
return false, err
|
||
}
|
||
// 无错误表示容器存在
|
||
return false, nil
|
||
}
|
||
func DeleteDevContainerByDocker(ctx context.Context, devContainerInfo *devcontainer_models.Devcontainer) error {
|
||
// 创建docker client
|
||
cli, err := docker_module.CreateDockerClient(ctx)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer cli.Close()
|
||
// 获取容器 ID
|
||
|
||
containerID, err := docker_module.GetContainerID(cli, devContainerInfo.Name)
|
||
if err != nil {
|
||
if errdefs.IsNotFound(err) {
|
||
return nil
|
||
}
|
||
return err
|
||
}
|
||
// 删除容器
|
||
if err := docker_module.DeleteContainer(ctx, cli, containerID); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
func RestartDevContainerByDocker(ctx context.Context, devContainerInfo *devcontainer_models.Devcontainer) error {
|
||
// 创建docker client
|
||
cli, err := docker_module.CreateDockerClient(ctx)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer cli.Close()
|
||
// 获取容器 ID
|
||
|
||
containerID, err := docker_module.GetContainerID(cli, devContainerInfo.Name)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
// restart容器
|
||
timeout := 10 // 超时时间(秒)
|
||
err = cli.ContainerRestart(context.Background(), containerID, container.StopOptions{
|
||
Timeout: &timeout,
|
||
})
|
||
if err != nil {
|
||
return err
|
||
} else {
|
||
log.Info("容器已重启")
|
||
}
|
||
return nil
|
||
}
|
||
func StopDevContainerByDocker(ctx context.Context, devContainerInfo *devcontainer_models.Devcontainer) error {
|
||
// 创建docker client
|
||
cli, err := docker_module.CreateDockerClient(ctx)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer cli.Close()
|
||
// 获取容器 ID
|
||
|
||
containerID, err := docker_module.GetContainerID(cli, devContainerInfo.Name)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
// stop容器
|
||
timeout := 10 // 超时时间(秒)
|
||
err = cli.ContainerStop(context.Background(), containerID, container.StopOptions{
|
||
Timeout: &timeout,
|
||
})
|
||
if err != nil {
|
||
return err
|
||
} else {
|
||
log.Info("容器已停止")
|
||
}
|
||
return nil
|
||
}
|
||
func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontainer_models.Devcontainer, updateInfo *UpdateInfo, repo *repo.Repository, doer *user.User) error {
|
||
// 创建docker client
|
||
cli, err := docker_module.CreateDockerClient(ctx)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer cli.Close()
|
||
|
||
// update容器
|
||
imageRef := updateInfo.RepositoryAddress + "/" + updateInfo.RepositoryUsername + "/" + updateInfo.ImageName
|
||
configurationString, err := GetDevcontainerConfigurationString(ctx, repo)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if updateInfo.SaveMethod == "on" {
|
||
// 创建构建上下文(包含Dockerfile的tar包)
|
||
var buf bytes.Buffer
|
||
tw := tar.NewWriter(&buf)
|
||
defer tw.Close()
|
||
// 添加Dockerfile到tar包
|
||
dockerfile := "Dockerfile"
|
||
dockerfileContent, err := GetFileContentByPath(ctx, repo, ".devcontainer/"+configurationModel.Build.Dockerfile)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
content := []byte(dockerfileContent)
|
||
header := &tar.Header{
|
||
Name: dockerfile,
|
||
Size: int64(len(content)),
|
||
Mode: 0644,
|
||
}
|
||
if err := tw.WriteHeader(header); err != nil {
|
||
return err
|
||
}
|
||
if _, err := tw.Write(content); err != nil {
|
||
return err
|
||
}
|
||
buildOptions := types.ImageBuildOptions{
|
||
Tags: []string{imageRef}, // 镜像标签
|
||
}
|
||
|
||
_, err = cli.ImageBuild(
|
||
context.Background(),
|
||
&buf,
|
||
buildOptions,
|
||
)
|
||
if err != nil {
|
||
log.Info(err.Error())
|
||
return err
|
||
}
|
||
|
||
} else {
|
||
// 获取容器 ID
|
||
containerID, err := docker_module.GetContainerID(cli, devContainerInfo.Name)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
// 提交容器
|
||
_, err = cli.ContainerCommit(ctx, containerID, types.ContainerCommitOptions{Reference: imageRef})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
// 推送到仓库
|
||
dockerHost, err := docker_module.GetDockerSocketPath()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
err = docker_module.PushImage(dockerHost, updateInfo.RepositoryUsername, updateInfo.PassWord, updateInfo.RepositoryAddress, imageRef)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 定义正则表达式来匹配 image 字段
|
||
re := regexp.MustCompile(`"image"\s*:\s*"([^"]+)"`)
|
||
// 使用正则表达式查找并替换 image 字段的值
|
||
newConfiguration := re.ReplaceAllString(configurationString, `"image": "`+imageRef+`"`)
|
||
err = UpdateDevcontainerConfiguration(newConfiguration, repo, doer)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ImageExists 检查指定镜像是否存在
|
||
// 返回值:
|
||
// - bool: 镜像是否存在(true=存在,false=不存在)
|
||
// - error: 非空表示检查过程中发生错误
|
||
func ImageExists(ctx context.Context, imageName string) (bool, error) {
|
||
|
||
// 创建 Docker 客户端
|
||
cli, err := docker_module.CreateDockerClient(ctx)
|
||
if err != nil {
|
||
return false, err // 其他错误
|
||
}
|
||
// 获取镜像信息
|
||
_, _, err = cli.ImageInspectWithRaw(ctx, imageName)
|
||
if err != nil {
|
||
return false, err // 其他错误
|
||
}
|
||
return true, nil // 镜像存在
|
||
}
|
||
func CheckDirExistsFromDocker(ctx context.Context, containerName, dirPath string) (bool, error) {
|
||
// 上下文
|
||
// 创建 Docker 客户端
|
||
cli, err := docker_module.CreateDockerClient(ctx)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
// 获取容器 ID
|
||
containerID, err := docker_module.GetContainerID(cli, containerName)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
// 创建 exec 配置
|
||
execConfig := types.ExecConfig{
|
||
Cmd: []string{"test", "-d", dirPath}, // 检查目录是否存在
|
||
AttachStdout: true,
|
||
AttachStderr: true,
|
||
}
|
||
|
||
// 创建 exec 实例
|
||
execResp, err := cli.ContainerExecCreate(context.Background(), containerID, execConfig)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
// 执行命令
|
||
var exitCode int
|
||
err = cli.ContainerExecStart(context.Background(), execResp.ID, types.ExecStartCheck{})
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
// 获取命令执行结果
|
||
resp, err := cli.ContainerExecInspect(context.Background(), execResp.ID)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
exitCode = resp.ExitCode
|
||
return exitCode == 0, nil // 退出码为 0 表示目录存在
|
||
}
|
||
func RegistWebTerminal(ctx context.Context) error {
|
||
log.Info("开始构建WebTerminal...")
|
||
cli, err := docker_module.CreateDockerClient(ctx)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer cli.Close()
|
||
//拉取web_terminal镜像
|
||
dockerHost, err := docker_module.GetDockerSocketPath()
|
||
if err != nil {
|
||
return fmt.Errorf("获取docker socket路径失败:%v", err)
|
||
}
|
||
// 拉取镜像
|
||
err = docker_module.PullImage(ctx, cli, dockerHost, setting.DevContainerConfig.Web_Terminal_Image)
|
||
if err != nil {
|
||
return fmt.Errorf("拉取web_terminal镜像失败:%v", err)
|
||
}
|
||
|
||
timestamp := time.Now().Format("20060102150405")
|
||
binds := []string{
|
||
"/var/run/docker.sock:/var/run/docker.sock",
|
||
}
|
||
containerName := "webterminal-" + timestamp
|
||
//创建并启动WebTerminal容器
|
||
err = docker_module.CreateAndStartContainer(ctx, cli, setting.DevContainerConfig.Web_Terminal_Image,
|
||
nil,
|
||
nil, binds,
|
||
nat.PortSet{
|
||
"7681/tcp": struct{}{},
|
||
},
|
||
containerName)
|
||
if err != nil {
|
||
return fmt.Errorf("创建并注册WebTerminal失败:%v", err)
|
||
}
|
||
// Save settings.
|
||
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
_, err = docker_module.GetMappedPort(ctx, containerName, "7681")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
cfg.Section("devcontainer").Key("WEB_TERMINAL_CONTAINER").SetValue(containerName)
|
||
if err = cfg.SaveTo(setting.CustomConf); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|