!46 [DIP-2][DevContainer] 添加 WebSSH 和 保存镜像功能、更新了DevContainer相关的配置
* 合并devcontainer web相关的源文件,简化目录结构 * devcontainer、ssh_key_pair和devcontainer.cloud * fixed bug:创建容器时Host为localhost时创建失败的问题 * 删除了死代码,更新了一些命名(主要是去掉devstar字符串) * 常量名全大写 * devcontainer HOST改为用户设置的域名或IP * 安装时如没有配置devcontainer则默认设置为docker方式 * 直接使用kubernetes和docker简化代码提高可读性 * 去除services/devstar_devcontainer文件夹名中不必要的devstar字符串 * 去除services/devstar_devcontainer文件夹名中不必要的devstar字符串 * 文件名中去掉不必要的devstar字符串 * 变量名中删除不必要的Devstar字符串 * Merge branch 'dev' into feature-websshAndUpdateImage * change pages style * change Structure * fix bug * websshAndUpdateImage
This commit is contained in:
repo.diff.committed_by
孟宁
repo.diff.parent
171bc80cd7
repo.diff.commit
e6d1dbb381
@@ -55,17 +55,17 @@ data:
|
||||
ui.admin: |
|
||||
DEV_CONTAINERS_PAGING_NUM = 50
|
||||
|
||||
devstar.devcontainer: |
|
||||
devcontainer: |
|
||||
ENABLED = true
|
||||
AGENT = k8s
|
||||
TIMEOUT_SECONDS = 120
|
||||
HOST = <k8s 暴露访问域名或IP,比如 devcontainer.devstar.cn >
|
||||
NAMESPACE = <k8s DevStar Studio 部署 namespace,比如 devstar-studio-ns >
|
||||
|
||||
devstar.ssh_key_pair: |
|
||||
ssh_key_pair: |
|
||||
KEY_SIZE = <写入希望生成的SSH密钥长度,比如 4096 ,默认值 2048>
|
||||
|
||||
devstar.cloud: |
|
||||
devcontainer.cloud: |
|
||||
ENABLED = true
|
||||
PROVIDER = tencent
|
||||
|
||||
@@ -87,7 +87,7 @@ stringData:
|
||||
WECHAT_OFFICIAL_ACCOUNT_MESSAGE_TOKEN = <微信公众号自定义Token>
|
||||
WECHAT_OFFICIAL_ACCOUNT_MESSAGE_AES_KEY = <微信公众号AES加密密钥>
|
||||
|
||||
devstar.cloud.tencent: |
|
||||
devcontainer.cloud.tencent: |
|
||||
ENDPOINT = <API访问端点名称,例如 vpc.tencentcloudapi.com>
|
||||
REGION = <区域代码,例如 ap-shanghai>
|
||||
NAT_GATEWAY_ID = <腾讯云控制台使用的 NAT网关 ID>
|
||||
@@ -258,7 +258,7 @@ subjects:
|
||||
> config, err = clientgorest.InClusterConfig()
|
||||
> if err != nil {
|
||||
> log.Error("Failed to obtain Kubernetes config both inside/outside of cluster, the DevContainer is Disabled")
|
||||
> setting.Devstar.Devcontainer.Enabled = false
|
||||
> setting.Devcontainer.Enabled = false
|
||||
> return nil, err
|
||||
> }
|
||||
> }
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package k8s_agent
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/devstar_cloud_provider"
|
||||
"code.gitea.io/gitea/services/devstar_devcontainer/errors"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/devcontainer/errors"
|
||||
"code.gitea.io/gitea/services/devstar_cloud_provider"
|
||||
|
||||
devcontainer_api_v1 "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/api/v1"
|
||||
devcontainer_dto "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/dto"
|
||||
devcontainer_k8s_agent_modules_errors "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/errors"
|
||||
@@ -138,7 +139,7 @@ func CreateDevcontainer(ctx *context.Context, client dynamic_client.Interface, o
|
||||
// test-mockrepo1-6c5369588f8911e-0 0/1 Init:CrashLoopBackOff 1 (5s ago) 7s
|
||||
// 需要删除刚刚创建的 k8s CRD,然后返回 DevContainer 初始化失败
|
||||
optsDeleteInitFailed := &devcontainer_dto.DeleteDevcontainerOptions{
|
||||
Namespace: setting.Devstar.Devcontainer.Namespace,
|
||||
Namespace: setting.Devcontainer.Namespace,
|
||||
Name: devcontainerApp.Name,
|
||||
}
|
||||
_ = DeleteDevcontainer(ctx, client, optsDeleteInitFailed)
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package k8s_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
devcontainer_api_v1 "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/api/v1"
|
||||
devcontainer_dto "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/dto"
|
||||
devcontainer_errors "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/errors"
|
||||
devcontainer_module_utils "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/utils"
|
||||
devcontainer_agent_module_vo "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/vo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"context"
|
||||
"fmt"
|
||||
apimachinery_api_metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
apimachinery_apis_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
apimachinery_apis_v1_unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -85,7 +86,7 @@ func waitUntilDevcontainerReadyWithTimeout(ctx *context.Context, client dynamic_
|
||||
}
|
||||
|
||||
// 1. 注册 watcher 监听 DevContainer Status 变化
|
||||
watcherTimeoutSeconds := setting.Devstar.Devcontainer.TimeoutSeconds
|
||||
watcherTimeoutSeconds := setting.Devcontainer.TimeoutSeconds
|
||||
watcher, err := client.Resource(groupVersionResource).Namespace(opts.Namespace).Watch(*ctx, apimachinery_apis_v1.ListOptions{
|
||||
FieldSelector: fmt.Sprintf("metadata.name=%s", opts.Name),
|
||||
Watch: true,
|
||||
@@ -154,6 +155,6 @@ func waitUntilDevcontainerReadyWithTimeout(ctx *context.Context, client dynamic_
|
||||
return nil, devcontainer_errors.ErrOpenDevcontainerTimeout{
|
||||
Name: opts.Name,
|
||||
Namespace: opts.Namespace,
|
||||
TimeoutSeconds: setting.Devstar.Devcontainer.TimeoutSeconds,
|
||||
TimeoutSeconds: setting.Devcontainer.TimeoutSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package k8s_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"context"
|
||||
|
||||
dynamicclient "k8s.io/client-go/dynamic"
|
||||
clientgorest "k8s.io/client-go/rest"
|
||||
@@ -21,7 +22,7 @@ func GetKubernetesClient(ctx *context.Context) (dynamicclient.Interface, error)
|
||||
config, err = clientgorest.InClusterConfig()
|
||||
if err != nil {
|
||||
log.Error("Failed to obtain Kubernetes config both inside/outside of cluster, the DevContainer is Disabled")
|
||||
setting.Devstar.Devcontainer.Enabled = false
|
||||
setting.Devcontainer.Enabled = false
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker_agent
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
devcontainer_dto "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/dto"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
devcontainer_service_dto "code.gitea.io/gitea/services/devstar_devcontainer/dto"
|
||||
devcontainer_service_dto "code.gitea.io/gitea/services/devcontainer/dto"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
@@ -46,9 +46,9 @@ func AssignDevcontainerCreationDockerOperator(ctx *context.Context, newDevContai
|
||||
CreateOptions: apimachinery_meta_v1.CreateOptions{},
|
||||
Image: newDevContainer.Image,
|
||||
CommandList: []string{
|
||||
"sh",
|
||||
"sh",
|
||||
"-c",
|
||||
"rm -f /etc/ssh/ssh_host_*; ssh-keygen -A ; service ssh start ; while true; do sleep 60; done",
|
||||
"rm -f /etc/ssh/ssh_host_*; ssh-keygen -A ; service ssh start ; cd ~/ttyd/build; ./ttyd -W bash;",
|
||||
},
|
||||
SSHPublicKeyList: newDevContainer.SSHPublicKeyList,
|
||||
GitRepositoryURL: newDevContainer.GitRepositoryURL,
|
||||
@@ -107,17 +107,26 @@ func createAndStartContainer(cli *client.Client, opts *devcontainer_dto.CreateDe
|
||||
Tty: true,
|
||||
OpenStdin: true,
|
||||
ExposedPorts: nat.PortSet{
|
||||
"22/tcp": struct{}{},
|
||||
"22/tcp": struct{}{},
|
||||
"7681/tcp": struct{}{},
|
||||
},
|
||||
}
|
||||
|
||||
host := setting.Devcontainer.Host
|
||||
if host == "localhost" {
|
||||
host = "127.0.0.1" // 或使用宿主机的真实 IP 地址
|
||||
}
|
||||
|
||||
// 设置容器主机配置
|
||||
hostConfig := &container.HostConfig{
|
||||
// PortBindings: nat.PortMap{
|
||||
// "22/tcp": []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: strconv.Itoa(int(opts.ServicePort))}}, // 将容器的22端口映射到宿主机的8080端口
|
||||
// },
|
||||
ExtraHosts: []string{"host.docker.internal:" + setting.Devstar.Devcontainer.Host},
|
||||
ExtraHosts: []string{"host.docker.internal:" + host},
|
||||
PublishAllPorts: true,
|
||||
RestartPolicy: container.RestartPolicy{
|
||||
Name: "always",
|
||||
},
|
||||
}
|
||||
|
||||
// 创建容器
|
||||
@@ -175,6 +184,11 @@ func createAndStartContainer(cli *client.Client, opts *devcontainer_dto.CreateDe
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("执行命令失败:%v", err)
|
||||
}
|
||||
// 启动执行实例
|
||||
err = cli.ContainerExecStart(context.Background(), ex.ID, types.ExecStartCheck{})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to start exec instance:%v", err)
|
||||
}
|
||||
output, err := ioutil.ReadAll(execResp.Reader)
|
||||
log.Info("Command output:\n%s\n", output)
|
||||
// 获取容器详细信息
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker_agent
|
||||
package docker
|
||||
|
||||
import (
|
||||
devstar_devcontainer_models "code.gitea.io/gitea/models/devstar_devcontainer"
|
||||
82
modules/docker/AssignDevcontainerGettingDockerOperator.go
Normal file
82
modules/docker/AssignDevcontainerGettingDockerOperator.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
devcontainer_service_errors "code.gitea.io/gitea/services/devcontainer/errors"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devcontainer/options"
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func AssignDevcontainerGettingDockerOperator(ctx *context.Context, opts *devcontainer_service_options.OpenDevcontainerAppDispatcherOptions) (uint16, error) {
|
||||
// 1. 创建docker client
|
||||
cli, err := CreateDockerClient(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if cli != nil {
|
||||
defer cli.Close()
|
||||
}
|
||||
|
||||
// 获取容器详细信息
|
||||
containerJSON, err := cli.ContainerInspect(context.Background(), opts.Name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 获取端口映射信息
|
||||
portBindings := containerJSON.NetworkSettings.Ports
|
||||
for containerPort, bindings := range portBindings {
|
||||
for _, binding := range bindings {
|
||||
log.Info("Container Port %s is mapped to Host Port %s on IP %s\n", containerPort, binding.HostPort, binding.HostIP)
|
||||
if containerPort.Port() == "22" {
|
||||
v, err := strconv.ParseUint(binding.HostPort, 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// 执行命令在容器内添加公钥
|
||||
execConfig := types.ExecConfig{
|
||||
Cmd: []string{"sh", "-c", fmt.Sprintf("echo '%s' >> ~/.ssh/authorized_keys", opts.UserPublicKey)},
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
}
|
||||
// 创建执行实例
|
||||
execID, err := cli.ContainerExecCreate(context.Background(), containerJSON.ID, execConfig)
|
||||
if err != nil {
|
||||
log.Info("Failed to create exec instance:", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 附加到执行实例
|
||||
attachConfig := types.ExecStartCheck{}
|
||||
resp, err := cli.ContainerExecAttach(context.Background(), execID.ID, attachConfig)
|
||||
if err != nil {
|
||||
log.Info("Failed to attach to exec instance:", err)
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Close()
|
||||
// 启动执行实例
|
||||
err = cli.ContainerExecStart(context.Background(), execID.ID, attachConfig)
|
||||
if err != nil {
|
||||
log.Info("Failed to start exec instance:", err)
|
||||
return 0, err
|
||||
}
|
||||
output, err := ioutil.ReadAll(resp.Reader)
|
||||
log.Info("Command output:\n%s\n", output)
|
||||
return uint16(v), nil
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return 0, devcontainer_service_errors.ErrOperateDevcontainer{
|
||||
Action: "Open DevContainer in docker",
|
||||
Message: "cannot find SSH containerPort 22 for DevContainer " + opts.Name,
|
||||
}
|
||||
}
|
||||
func addTemporaryPublicKey() {
|
||||
|
||||
}
|
||||
97
modules/docker/AssignDevcontainerUpdateDockerOperator.go
Normal file
97
modules/docker/AssignDevcontainerUpdateDockerOperator.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devcontainer/options"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func AssignDevcontainerUpdateDockerOperator(ctx *gitea_web_context.Context, opts *devcontainer_service_options.UpdateDevcontainerOptions) {
|
||||
// 1. 创建docker client
|
||||
reqctx := ctx.Req.Context()
|
||||
cli, err := CreateDockerClient(&reqctx)
|
||||
defer cli.Close()
|
||||
if err != nil {
|
||||
ctx.JSON(500, map[string]string{
|
||||
"message": "error CreateDockerClient"})
|
||||
return
|
||||
}
|
||||
dockerSocketPath, err := GetDockerSocketPath()
|
||||
if err != nil {
|
||||
ctx.JSON(500, map[string]string{
|
||||
"message": "error GetDockerSocketPath"})
|
||||
return
|
||||
}
|
||||
script := "docker " + "-H " + dockerSocketPath + " login -u " + opts.RepositoryUsername + " -p " + opts.PassWord + " " + opts.RepositoryAddress + " "
|
||||
log.Info(string(script))
|
||||
cmd := exec.Command("sh", "-c", script)
|
||||
output, err := cmd.CombinedOutput()
|
||||
log.Info(string(output))
|
||||
if err != nil {
|
||||
log.Info("error RegistryLogin:", err)
|
||||
ctx.JSON(500, map[string]string{
|
||||
"message": "error RegistryLogin"})
|
||||
return
|
||||
}
|
||||
// 获取容器详细信息
|
||||
containerJSON, err := cli.ContainerInspect(context.Background(), opts.DevContainerName)
|
||||
if err != nil {
|
||||
log.Info("error ContainerInspect:", err)
|
||||
ctx.JSON(500, map[string]string{
|
||||
"message": "error ContainerInspect"})
|
||||
return
|
||||
}
|
||||
imageRef := opts.RepositoryAddress + "/" + opts.RepositoryUsername + "/" + opts.ImageName
|
||||
commitResp, err := cli.ContainerCommit(ctx, containerJSON.ID, types.ContainerCommitOptions{Reference: imageRef})
|
||||
log.Info(commitResp.ID)
|
||||
if err != nil {
|
||||
log.Info("error ContainerCommit:", err)
|
||||
ctx.JSON(500, map[string]string{
|
||||
"message": "error ContainerCommit"})
|
||||
return
|
||||
}
|
||||
// 推送到仓库
|
||||
script = "docker " + "-H " + dockerSocketPath + " push " + imageRef
|
||||
log.Info(string(script))
|
||||
cmd = exec.Command("sh", "-c", script)
|
||||
output, err = cmd.CombinedOutput()
|
||||
log.Info(string(output))
|
||||
if err != nil {
|
||||
log.Info("error docker push:", err)
|
||||
ctx.JSON(500, map[string]string{
|
||||
"message": "error docker push",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
jsonString := `{"image":"` + imageRef + `"}`
|
||||
resp, err := files_service.ChangeRepoFiles(db.DefaultContext, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
|
||||
Files: []*files_service.ChangeRepoFile{
|
||||
{
|
||||
Operation: "update",
|
||||
TreePath: ".devcontainer/devcontainer.json",
|
||||
ContentReader: bytes.NewReader([]byte(jsonString)),
|
||||
},
|
||||
},
|
||||
OldBranch: ctx.Repo.Repository.DefaultBranch,
|
||||
Message: "Update container",
|
||||
})
|
||||
log.Info(resp.Commit.URL)
|
||||
if err != nil {
|
||||
log.Info("error ChangeRepoFiles:", err)
|
||||
ctx.JSON(500, map[string]string{
|
||||
"message": "error ChangeRepoFiles"})
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, map[string]string{
|
||||
"redirect": ctx.Repo.RepoLink + "/dev-container",
|
||||
"message": "success"})
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
package docker_agent
|
||||
package docker
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/docker/docker/client"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
func CreateDockerClient(ctx *context.Context) (*client.Client, error) {
|
||||
@@ -30,8 +32,8 @@ func CreateDockerClient(ctx *context.Context) (*client.Client, error) {
|
||||
Docker环境路径优先级: 配置文件、环境变量
|
||||
*/
|
||||
func GetDockerSocketPath() (string, error) {
|
||||
if setting.Devstar.Devcontainer.DockerHost != "" {
|
||||
return setting.Devstar.Devcontainer.DockerHost, nil
|
||||
if setting.Devcontainer.DockerHost != "" {
|
||||
return setting.Devcontainer.DockerHost, nil
|
||||
}
|
||||
socket, found := os.LookupEnv("DOCKER_HOST")
|
||||
if found {
|
||||
@@ -66,3 +68,32 @@ func CheckIfDockerRunning(ctx context.Context, configDockerHost string) (*client
|
||||
}
|
||||
return cli, nil
|
||||
}
|
||||
func GetSSHPort(ctx *context.Context, name string) uint16 {
|
||||
// 1. 创建docker client
|
||||
cli, err := CreateDockerClient(ctx)
|
||||
defer cli.Close()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
// 获取容器详细信息
|
||||
containerJSON, err := cli.ContainerInspect(context.Background(), name)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 获取端口映射信息
|
||||
portBindings := containerJSON.NetworkSettings.Ports
|
||||
for containerPort, bindings := range portBindings {
|
||||
for _, binding := range bindings {
|
||||
log.Info("Container Port %s is mapped to Host Port %s on IP %s\n", containerPort, binding.HostPort, binding.HostIP)
|
||||
if containerPort.Port() == "7681" {
|
||||
v, err := strconv.ParseUint(binding.HostPort, 10, 16)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return uint16(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
177
modules/setting/devcontainer.go
Normal file
177
modules/setting/devcontainer.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
const (
|
||||
KUBERNETES = "kubernetes" // 支持 "k8s" 和 "kubernetes"
|
||||
DOCKER = "docker"
|
||||
)
|
||||
|
||||
// 检查用户输入的 DevContainer Agent 是否有效
|
||||
func isValidAgent(agent string) bool {
|
||||
return agent == "k8s" || agent == KUBERNETES || agent == DOCKER
|
||||
}
|
||||
|
||||
const (
|
||||
CLOUD_PROVIDER_TENCENT = "tencent"
|
||||
DEVCONTAINER_CLOUD_NAT_RULE_DESCRIPTION_PREFIX = "DevContainer: "
|
||||
)
|
||||
|
||||
// validCloudProviderSet 私有 Set 结构,标识目前系统所有支持的 Cloud Provider 类型
|
||||
var validCloudProviderSet = map[string]struct{}{
|
||||
CLOUD_PROVIDER_TENCENT: {},
|
||||
}
|
||||
|
||||
type DevcontainerType struct {
|
||||
Enabled bool
|
||||
Host string
|
||||
Agent string
|
||||
Namespace string
|
||||
TimeoutSeconds int64
|
||||
|
||||
DefaultGitBranchName string
|
||||
DefaultDevcontainerImageName string
|
||||
DockerHost string
|
||||
}
|
||||
|
||||
type SSHKeyPairType struct {
|
||||
KeySize int
|
||||
}
|
||||
|
||||
type CloudType struct {
|
||||
Enabled bool
|
||||
Provider string
|
||||
Tencent CloudProviderTencentType `ini:"devcontainer.cloud.tencent"`
|
||||
}
|
||||
|
||||
type CloudProviderTencentType struct {
|
||||
Endpoint string
|
||||
Region string
|
||||
NatGatewayId string
|
||||
PublicIpAddress string
|
||||
PrivateIpAddress string
|
||||
IpProtocol string
|
||||
SecretId string
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
var Devcontainer = DevcontainerType{
|
||||
Enabled: false,
|
||||
Namespace: "default",
|
||||
TimeoutSeconds: 900, // Max wait time for DevContainer to be ready (blocking), default is 15 minutes, can be overridden by app.ini
|
||||
|
||||
DefaultGitBranchName: "main", // Default branch name for .devcontainer/devcontainer.json
|
||||
|
||||
DefaultDevcontainerImageName: "devstar.cn/public/base-ssh-devcontainer:ubuntu-20.04-20241014", // Default image if not specified
|
||||
}
|
||||
|
||||
var SSHKeypair = SSHKeyPairType{
|
||||
KeySize: 2048, // Size of the SSH key
|
||||
}
|
||||
|
||||
var Cloud = CloudType{
|
||||
Enabled: false, // Cloud feature toggle
|
||||
}
|
||||
|
||||
// validateDevcontainerSettings 检查从 ini 配置文件中读取 DevStar DevContainer 配置信息,若数据无效,则自动禁用 DevContainer
|
||||
func validateDevcontainerSettings() {
|
||||
|
||||
// 检查 Host 是否为空,若为空,则自动将 DevContainer 设置为禁用
|
||||
if len(Devcontainer.Host) == 0 {
|
||||
log.Warn("INVALID config 'host' for DevStar DevContainer")
|
||||
Devcontainer.Enabled = false
|
||||
}
|
||||
|
||||
// 检查用户输入的 DevContainer Agent 是否存在支持列表,若不支持,则将 DevContainer 设置为禁用
|
||||
if !isValidAgent(Devcontainer.Agent) {
|
||||
log.Warn("Invalid config 'agent' for DevStar DevContainer")
|
||||
Devcontainer.Enabled = false
|
||||
}
|
||||
|
||||
// 检查默认分支名称设置
|
||||
if len(Devcontainer.DefaultGitBranchName) == 0 {
|
||||
log.Warn("INVALID config 'DefaultGitBranchName' for DevStar DevContainer")
|
||||
Devcontainer.Enabled = false
|
||||
}
|
||||
|
||||
// 检查默认 DevContainer Image
|
||||
if len(Devcontainer.DefaultDevcontainerImageName) == 0 {
|
||||
log.Warn("INVALID config 'DefaultGitBranchNameDefaultDevcontainerImageName' for DevStar DevContainer")
|
||||
Devcontainer.Enabled = false
|
||||
}
|
||||
|
||||
if Devcontainer.Enabled == false {
|
||||
log.Warn("DevStar DevContainer Service Disabled")
|
||||
} else {
|
||||
log.Info("DevStar DevContainer Service Enabled")
|
||||
}
|
||||
}
|
||||
|
||||
// validateSSHKeyPairSettings 检查从 ini 配置文件中读取 DevStar SSH Key Pair 配置信息
|
||||
func validateSSHKeyPairSettings() {
|
||||
if SSHKeypair.KeySize < 1024 {
|
||||
SSHKeypair.KeySize = 1024
|
||||
}
|
||||
}
|
||||
|
||||
// validateDevcontainerCloudSettings 检查从 ini 配置文件中读取 DevStar Cloud 配置信息
|
||||
func validateDevcontainerCloudSettings() {
|
||||
switch Cloud.Provider {
|
||||
case CLOUD_PROVIDER_TENCENT:
|
||||
// 腾讯云配置检查
|
||||
|
||||
if len(Cloud.Tencent.NatGatewayId) < 4 {
|
||||
log.Warn("INVALID NAT Gateway ID '%v' for DevStar Cloud Provider Tencent", Cloud.Tencent.NatGatewayId)
|
||||
Cloud.Enabled = false
|
||||
}
|
||||
|
||||
if Cloud.Tencent.IpProtocol != "TCP" && Cloud.Tencent.IpProtocol != "UDP" {
|
||||
log.Warn("INVALID IP Protocol '%v' for DevStar Cloud Provider Tencent", Cloud.Tencent.IpProtocol)
|
||||
Cloud.Enabled = false
|
||||
}
|
||||
|
||||
if len(Cloud.Tencent.Region) < 3 || len(Cloud.Tencent.Endpoint) == 0 {
|
||||
log.Warn("INVALID (Region, Endpoint) pair ('%v', '%v') for DevStar Cloud Provider Tencent",
|
||||
Cloud.Tencent.Region, Cloud.Tencent.Endpoint)
|
||||
Cloud.Enabled = false
|
||||
}
|
||||
|
||||
if len(Cloud.Tencent.PrivateIpAddress) == 0 || len(Cloud.Tencent.PublicIpAddress) == 0 {
|
||||
log.Warn("INVALID (PublicIpAddress, PrivateIpAddress) pair ('%v', '%v') for DevStar Cloud Provider Tencent",
|
||||
Cloud.Tencent.PublicIpAddress, Cloud.Tencent.PrivateIpAddress)
|
||||
Cloud.Enabled = false
|
||||
}
|
||||
|
||||
if len(Cloud.Tencent.SecretId) == 0 || len(Cloud.Tencent.SecretKey) == 0 {
|
||||
log.Warn("INVALID (SecretId, SecretKey) pair for DevStar Cloud Provider Tencent")
|
||||
Cloud.Enabled = false
|
||||
}
|
||||
|
||||
default:
|
||||
// 无效 Cloud Provider 名称
|
||||
log.Warn("INVALID config '%v' for DevStar Cloud", Cloud.Provider)
|
||||
Cloud.Enabled = false
|
||||
}
|
||||
|
||||
if Cloud.Enabled == false {
|
||||
log.Warn("DevStar Cloud Provider Service Disabled")
|
||||
} else {
|
||||
log.Info("DevStar Cloud Provider '%v' Enabled", Cloud.Provider)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func loadDevcontainerFrom(rootCfg ConfigProvider) {
|
||||
mustMapSetting(rootCfg, "devcontainer", &Devcontainer)
|
||||
validateDevcontainerSettings()
|
||||
|
||||
mustMapSetting(rootCfg, "ssh_key_pair", &SSHKeypair)
|
||||
validateSSHKeyPairSettings()
|
||||
|
||||
if Devcontainer.Agent == "k8s" || Devcontainer.Agent == KUBERNETES {
|
||||
mustMapSetting(rootCfg, "devcontainer.cloud", &Cloud)
|
||||
validateDevcontainerCloudSettings()
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
const (
|
||||
DEVCONTAINER_AGENT_NAME_K8S string = "k8s"
|
||||
DEVCONTAINER_AGENT_NAME_DOCKER string = "docker"
|
||||
)
|
||||
|
||||
// package 内部私有变量,是一个 Set 结构,标识目前系统所有支持的 DevContainer Agent 类型
|
||||
var validDevcontainerAgentSet = map[string]struct{}{
|
||||
DEVCONTAINER_AGENT_NAME_K8S: {},
|
||||
DEVCONTAINER_AGENT_NAME_DOCKER: {},
|
||||
}
|
||||
|
||||
const (
|
||||
CLOUD_PROVIDER_TENCENT = "tencent"
|
||||
DEVCONTAINER_CLOUD_NAT_RULE_DESCRIPTION_PREFIX = "DevContainer: "
|
||||
)
|
||||
|
||||
// validCloudProviderSet 私有 Set 结构,标识目前系统所有支持的 Cloud Provider 类型
|
||||
var validCloudProviderSet = map[string]struct{}{
|
||||
CLOUD_PROVIDER_TENCENT: {},
|
||||
}
|
||||
|
||||
var Devstar = struct {
|
||||
Devcontainer DevcontainerType `ini:"devstar.devcontainer"`
|
||||
SSHKeypair SSHKeyPairType `ini:"devstar.ssh_key_pair"`
|
||||
Cloud CloudType `ini:"devstar.cloud"`
|
||||
}{
|
||||
Devcontainer: DevcontainerType{
|
||||
Enabled: false,
|
||||
Namespace: "default",
|
||||
TimeoutSeconds: 900, // 最长等待 DevContainer 就绪时间(阻塞式),默认15分钟,可被 app.ini 指定值覆盖
|
||||
|
||||
// 获取 .devcontainer/devcontainer.json 默认分支名称
|
||||
DefaultGitBranchName: "main",
|
||||
|
||||
// 若 .devcontainer/devcontainer.json 默认分支未指定 DevContainer Image (`image`),则使用如下默认值
|
||||
// 注: 该镜像必须含有 OpenSSH 服务器程序,否则需要手动制作
|
||||
// 手动制作方式参考: https://gitee.com/devstar/devcontainer-tweak
|
||||
DefaultDevcontainerImageName: "devstar.cn/public/base-ssh-devcontainer:ubuntu-20.04-20241014",
|
||||
},
|
||||
SSHKeypair: SSHKeyPairType{
|
||||
KeySize: 2048,
|
||||
},
|
||||
Cloud: CloudType{
|
||||
Enabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
type DevcontainerType struct {
|
||||
Enabled bool
|
||||
Host string
|
||||
Agent string
|
||||
Namespace string
|
||||
TimeoutSeconds int64
|
||||
|
||||
DefaultGitBranchName string
|
||||
DefaultDevcontainerImageName string
|
||||
DockerHost string
|
||||
}
|
||||
|
||||
type SSHKeyPairType struct {
|
||||
KeySize int
|
||||
}
|
||||
|
||||
type CloudType struct {
|
||||
Enabled bool
|
||||
Provider string
|
||||
Tencent CloudProviderTencentType `ini:"devstar.cloud.tencent"`
|
||||
}
|
||||
|
||||
type CloudProviderTencentType struct {
|
||||
Endpoint string
|
||||
Region string
|
||||
NatGatewayId string
|
||||
PublicIpAddress string
|
||||
PrivateIpAddress string
|
||||
IpProtocol string
|
||||
SecretId string
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
// validateDevstarDevcontainerSettings 检查从 ini 配置文件中读取 DevStar DevContainer 配置信息,若数据无效,则自动禁用 DevContainer
|
||||
func validateDevstarDevcontainerSettings() {
|
||||
|
||||
// 检查 Host 是否为空,若为空,则自动将 DevContainer 设置为禁用
|
||||
if len(Devstar.Devcontainer.Host) == 0 {
|
||||
log.Warn("INVALID config 'host' for DevStar DevContainer")
|
||||
Devstar.Devcontainer.Enabled = false
|
||||
}
|
||||
|
||||
// 检查用户输入的 DevContainer Agent 是否存在支持列表,若不支持,则将 DevContainer 设置为禁用
|
||||
if _, exists := validDevcontainerAgentSet[Devstar.Devcontainer.Agent]; !exists {
|
||||
log.Warn("INVALID config 'agent' for DevStar DevContainer")
|
||||
Devstar.Devcontainer.Enabled = false
|
||||
}
|
||||
|
||||
// 检查默认分支名称设置
|
||||
if len(Devstar.Devcontainer.DefaultGitBranchName) == 0 {
|
||||
log.Warn("INVALID config 'DefaultGitBranchName' for DevStar DevContainer")
|
||||
Devstar.Devcontainer.Enabled = false
|
||||
}
|
||||
|
||||
// 检查默认 DevContainer Image
|
||||
if len(Devstar.Devcontainer.DefaultDevcontainerImageName) == 0 {
|
||||
log.Warn("INVALID config 'DefaultGitBranchNameDefaultDevcontainerImageName' for DevStar DevContainer")
|
||||
Devstar.Devcontainer.Enabled = false
|
||||
}
|
||||
|
||||
if Devstar.Devcontainer.Enabled == false {
|
||||
log.Warn("DevStar DevContainer Service Disabled")
|
||||
} else {
|
||||
log.Info("DevStar DevContainer Service Enabled")
|
||||
}
|
||||
}
|
||||
|
||||
// validateDevstarSSHKeyPairSettings 检查从 ini 配置文件中读取 DevStar SSH Key Pair 配置信息
|
||||
func validateDevstarSSHKeyPairSettings() {
|
||||
if Devstar.SSHKeypair.KeySize < 1024 {
|
||||
Devstar.SSHKeypair.KeySize = 1024
|
||||
}
|
||||
}
|
||||
|
||||
// validateDevstarCloudSettings 检查从 ini 配置文件中读取 DevStar Cloud 配置信息
|
||||
func validateDevstarCloudSettings() {
|
||||
switch Devstar.Cloud.Provider {
|
||||
case CLOUD_PROVIDER_TENCENT:
|
||||
// 腾讯云配置检查
|
||||
|
||||
if len(Devstar.Cloud.Tencent.NatGatewayId) < 4 {
|
||||
log.Warn("INVALID NAT Gateway ID '%v' for DevStar Cloud Provider Tencent", Devstar.Cloud.Tencent.NatGatewayId)
|
||||
Devstar.Cloud.Enabled = false
|
||||
}
|
||||
|
||||
if Devstar.Cloud.Tencent.IpProtocol != "TCP" && Devstar.Cloud.Tencent.IpProtocol != "UDP" {
|
||||
log.Warn("INVALID IP Protocol '%v' for DevStar Cloud Provider Tencent", Devstar.Cloud.Tencent.IpProtocol)
|
||||
Devstar.Cloud.Enabled = false
|
||||
}
|
||||
|
||||
if len(Devstar.Cloud.Tencent.Region) < 3 || len(Devstar.Cloud.Tencent.Endpoint) == 0 {
|
||||
log.Warn("INVALID (Region, Endpoint) pair ('%v', '%v') for DevStar Cloud Provider Tencent",
|
||||
Devstar.Cloud.Tencent.Region, Devstar.Cloud.Tencent.Endpoint)
|
||||
Devstar.Cloud.Enabled = false
|
||||
}
|
||||
|
||||
if len(Devstar.Cloud.Tencent.PrivateIpAddress) == 0 || len(Devstar.Cloud.Tencent.PublicIpAddress) == 0 {
|
||||
log.Warn("INVALID (PublicIpAddress, PrivateIpAddress) pair ('%v', '%v') for DevStar Cloud Provider Tencent",
|
||||
Devstar.Cloud.Tencent.PublicIpAddress, Devstar.Cloud.Tencent.PrivateIpAddress)
|
||||
Devstar.Cloud.Enabled = false
|
||||
}
|
||||
|
||||
if len(Devstar.Cloud.Tencent.SecretId) == 0 || len(Devstar.Cloud.Tencent.SecretKey) == 0 {
|
||||
log.Warn("INVALID (SecretId, SecretKey) pair for DevStar Cloud Provider Tencent")
|
||||
Devstar.Cloud.Enabled = false
|
||||
}
|
||||
|
||||
default:
|
||||
// 无效 Cloud Provider 名称
|
||||
log.Warn("INVALID config '%v' for DevStar Cloud", Devstar.Cloud.Provider)
|
||||
Devstar.Cloud.Enabled = false
|
||||
}
|
||||
|
||||
if Devstar.Cloud.Enabled == false {
|
||||
log.Warn("DevStar Cloud Provider Service Disabled")
|
||||
} else {
|
||||
log.Info("DevStar Cloud Provider '%v' Enabled", Devstar.Cloud.Provider)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func loadDevstarFrom(rootCfg ConfigProvider) {
|
||||
mustMapSetting(rootCfg, "devstar", &Devstar)
|
||||
|
||||
validateDevstarDevcontainerSettings()
|
||||
validateDevstarSSHKeyPairSettings()
|
||||
validateDevstarCloudSettings()
|
||||
}
|
||||
@@ -217,7 +217,7 @@ func LoadSettings() {
|
||||
loadMimeTypeMapFrom(CfgProvider)
|
||||
loadFederationFrom(CfgProvider)
|
||||
loadWechatSettingsFrom(CfgProvider)
|
||||
loadDevstarFrom(CfgProvider)
|
||||
loadDevcontainerFrom(CfgProvider)
|
||||
}
|
||||
|
||||
// LoadSettingsForInstall initializes the settings for install
|
||||
@@ -226,6 +226,7 @@ func LoadSettingsForInstall() {
|
||||
loadServiceFrom(CfgProvider)
|
||||
loadMailerFrom(CfgProvider)
|
||||
loadWechatSettingsFrom(CfgProvider)
|
||||
loadDevcontainerFrom(CfgProvider)
|
||||
}
|
||||
|
||||
var configuredPaths = make(map[string]string)
|
||||
|
||||
@@ -1024,6 +1024,7 @@ visibility.private_tooltip = Visible only to members of organizations you have j
|
||||
dev_container = Dev Container
|
||||
dev_container_empty = Oops, it looks like there is no Dev Container in this repository.
|
||||
dev_container_invalid_config_prompt = Invalid Dev Container Configuration: Please upload a valid 'devcontainer.json' file to the default branch, and ensure that this repository is NOT archived.
|
||||
dev_container_control.update = Save Dev Container
|
||||
dev_container_control.create = Create Dev Container
|
||||
dev_container_control.creation_success_for_user = The Dev Container has been created successfully for user '%s'.
|
||||
dev_container_control.creation_failed_for_user = Failed to create the Dev Container for user '%s'.
|
||||
|
||||
@@ -1022,6 +1022,7 @@ visibility.private_tooltip=仅对您已加入的组织的成员可见。
|
||||
dev_container = 开发容器
|
||||
dev_container_empty = 您还没有该仓库的开发容器
|
||||
dev_container_invalid_config_prompt = 开发容器配置无效:请上传有效的 devcontainer.json 至默认分支,且确保仓库未处于存档状态
|
||||
dev_container_control.update = 保存开发容器
|
||||
dev_container_control.create = 创建开发容器
|
||||
dev_container_control.creation_success_for_user = 用户 '%s' 已成功创建开发容器
|
||||
dev_container_control.creation_failed_for_user = 用户 '%s' 开发容器创建失败
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
gitea_web_module "code.gitea.io/gitea/modules/web"
|
||||
Result "code.gitea.io/gitea/routers/entity"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
devcontainer_api_service "code.gitea.io/gitea/services/devstar_devcontainer/api_services"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devstar_devcontainer/options"
|
||||
devcontainer_api_service "code.gitea.io/gitea/services/devcontainer/api_services"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devcontainer/options"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// CreateRepoDevcontainer 创建 某用户在某仓库的 DevContainer
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
Result "code.gitea.io/gitea/routers/entity"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
devcontainer_api_service "code.gitea.io/gitea/services/devstar_devcontainer/api_services"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devstar_devcontainer/options"
|
||||
"strconv"
|
||||
devcontainer_api_service "code.gitea.io/gitea/services/devcontainer/api_services"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devcontainer/options"
|
||||
)
|
||||
|
||||
// DeleteRepoDevcontainer 删除某仓库的 DevContainer
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
Result "code.gitea.io/gitea/routers/entity"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
devcontainer_api_service "code.gitea.io/gitea/services/devstar_devcontainer/api_services"
|
||||
"code.gitea.io/gitea/services/devstar_devcontainer/options"
|
||||
"strconv"
|
||||
devcontainer_api_service "code.gitea.io/gitea/services/devcontainer/api_services"
|
||||
"code.gitea.io/gitea/services/devcontainer/options"
|
||||
)
|
||||
|
||||
// GetDevcontainer 查找某用户在某仓库的 DevContainer
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
// 请求体参数:
|
||||
// -- repoId: 需要为哪个仓库创建 DevContainer
|
||||
// -- wait: 是否等待 DevContainer 就绪(默认为 false 直接返回“未就绪”,否则阻塞等待)
|
||||
// -- UserPublicKey
|
||||
// 注意:必须携带 用户登录凭证
|
||||
func GetDevcontainer(ctx *gitea_web_context.Context) {
|
||||
// 1. 检查用户登录状态,若未登录则返回未授权错误
|
||||
@@ -25,6 +27,7 @@ func GetDevcontainer(ctx *gitea_web_context.Context) {
|
||||
// 2. 取得参数
|
||||
wait := ctx.FormBool("wait")
|
||||
repoIdStr := ctx.FormString("repoId")
|
||||
UserPublicKey := ctx.FormString("UserPublicKey")
|
||||
repoId, err := strconv.ParseInt(repoIdStr, 10, 64)
|
||||
if err != nil || repoId <= 0 {
|
||||
Result.RespFailedIllegalParams.RespondJson2HttpResponseWriter(ctx.Resp)
|
||||
@@ -33,9 +36,10 @@ func GetDevcontainer(ctx *gitea_web_context.Context) {
|
||||
|
||||
// 3. 准备调用 API Service 层,获取 DevContainer 信息
|
||||
optsAbstractOpenDevcontainer := &options.AbstractOpenDevcontainerOptions{
|
||||
Wait: wait,
|
||||
RepoId: repoId,
|
||||
Actor: ctx.Doer,
|
||||
Wait: wait,
|
||||
RepoId: repoId,
|
||||
Actor: ctx.Doer,
|
||||
UserPublicKey: UserPublicKey,
|
||||
}
|
||||
repoDevcontainerVO, err := devcontainer_api_service.OpenDevcontainerAPIService(ctx, optsAbstractOpenDevcontainer)
|
||||
if err != nil {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
DevcontainersVO "code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
Result "code.gitea.io/gitea/routers/entity"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
devstar_devcontainer_service "code.gitea.io/gitea/services/devstar_devcontainer"
|
||||
devstar_devcontainer_service "code.gitea.io/gitea/services/devcontainer"
|
||||
)
|
||||
|
||||
// ListUserDevcontainers 枚举已登录用户所有的 DevContainers
|
||||
|
||||
8
routers/api/devcontainer/vo/UpdateRepoDevcontainerVO.go
Normal file
8
routers/api/devcontainer/vo/UpdateRepoDevcontainerVO.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package vo
|
||||
|
||||
type UpdateInfo struct {
|
||||
ImageName string `json:"ImageName"`
|
||||
PassWord string `json:"RepositoryPassword"`
|
||||
RepositoryAddress string `json:"RepositoryAddress"`
|
||||
RepositoryUsername string `json:"RepositoryUsername"`
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package key_pair
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
Result "code.gitea.io/gitea/routers/entity"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/devstar_ssh_key_pair/api_service"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// GenerateNewSSHSessionKeyPair 创建临时会话 SSH 密钥对
|
||||
@@ -26,7 +27,7 @@ func GenerateNewSSHSessionKeyPair(ctx *gitea_web_context.Context) {
|
||||
Msg: Result.RespSSHKeyPairGenFailed.Msg,
|
||||
Data: map[string]string{
|
||||
"ErrorMsg": err.Error(),
|
||||
"KeySize": strconv.Itoa(setting.Devstar.SSHKeypair.KeySize),
|
||||
"KeySize": strconv.Itoa(setting.SSHKeypair.KeySize),
|
||||
},
|
||||
}
|
||||
resp.RespondJson2HttpResponseWriter(ctx.Resp)
|
||||
|
||||
@@ -420,6 +420,13 @@ func SubmitInstall(ctx *context.Context) {
|
||||
cfg.Section("server").Key("LFS_START_SERVER").SetValue("false")
|
||||
}
|
||||
|
||||
if !setting.Devcontainer.Enabled {
|
||||
cfg.Section("devcontainer").Key("ENABLED").SetValue("true")
|
||||
cfg.Section("devcontainer").Key("AGENT").SetValue("docker")
|
||||
cfg.Section("devcontainer").Key("HOST").SetValue(form.Domain)
|
||||
cfg.Section("devcontainer").Key("TIMEOUT_SECONDS").SetValue("120")
|
||||
}
|
||||
|
||||
if len(strings.TrimSpace(form.SMTPAddr)) > 0 {
|
||||
if _, err := mail.ParseAddress(form.SMTPFrom); err != nil {
|
||||
ctx.RenderWithErr(ctx.Tr("install.smtp_from_invalid"), tplInstall, &form)
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
DevcontainersVO "code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
DevcontainersService "code.gitea.io/gitea/services/devstar_devcontainer"
|
||||
)
|
||||
|
||||
// CreateRepoDevContainer 创建仓库 Dev Container
|
||||
func CreateRepoDevContainer(ctx *gitea_web_context.Context) {
|
||||
if !isUserDevcontainerAlreadyInRepository(ctx) {
|
||||
opts := &DevcontainersVO.CreateRepoDevcontainerOptions{
|
||||
Actor: ctx.Doer,
|
||||
Repository: ctx.Repo.Repository,
|
||||
}
|
||||
err := DevcontainersService.CreateRepoDevcontainer(ctx, opts)
|
||||
if err != nil {
|
||||
log.Error("failed to create devContainer with option{%v}: %v", opts, err.Error())
|
||||
ctx.Flash.Error(ctx.Tr("repo.dev_container_control.creation_failed_for_user", ctx.Doer.Name))
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.dev_container_control.creation_success_for_user", ctx.Doer.Name))
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/dev-container")
|
||||
}
|
||||
|
||||
// isValidRepoDevcontainerJsonFile 辅助判断当前仓库的当前分支是否存在有效的 /.devcontainer/devcontainer.json
|
||||
func isValidRepoDevcontainerJsonFile(ctx *gitea_web_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
|
||||
}
|
||||
|
||||
// isUserDevcontainerAlreadyInRepository 辅助判断当前用户在当前仓库是否已有 Dev Container
|
||||
func isUserDevcontainerAlreadyInRepository(ctx *gitea_web_context.Context) bool {
|
||||
|
||||
opts := &DevcontainersVO.RepoDevcontainerOptions{
|
||||
Actor: ctx.Doer,
|
||||
Repository: ctx.Repo.Repository,
|
||||
}
|
||||
|
||||
devcontainerDetails, _ := DevcontainersService.GetRepoDevcontainerDetails(ctx, opts)
|
||||
return devcontainerDetails.DevContainerId > 0
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
DevcontainersVO "code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
DevcontainersService "code.gitea.io/gitea/services/devstar_devcontainer"
|
||||
)
|
||||
|
||||
// DeleteRepoDevContainerForCurrentActor 删除仓库 当前用户 Dev Container
|
||||
func DeleteRepoDevContainerForCurrentActor(ctx *gitea_web_context.Context) {
|
||||
|
||||
if isUserDevcontainerAlreadyInRepository(ctx) {
|
||||
opts := &DevcontainersVO.RepoDevcontainerOptions{
|
||||
Actor: ctx.Doer,
|
||||
Repository: ctx.Repo.Repository,
|
||||
}
|
||||
err := DevcontainersService.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")
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
DevcontainersVO "code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
DevcontainersService "code.gitea.io/gitea/services/devstar_devcontainer"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
tplGetRepoDevcontainerDetail base.TplName = "repo/devcontainer/details"
|
||||
)
|
||||
|
||||
// GetRepoDevContainerDetails 获取仓库 Dev Container 详细信息
|
||||
func GetRepoDevContainerDetails(ctx *gitea_web_context.Context) {
|
||||
|
||||
// 1. 查询当前 Repo 已有的 Dev Container 信息
|
||||
opts := &DevcontainersVO.RepoDevcontainerOptions{
|
||||
Actor: ctx.Doer,
|
||||
Repository: ctx.Repo.Repository,
|
||||
}
|
||||
//ctx.Repo.RepoLink == ctx.Repo.Repository.Link()
|
||||
devContainerMetadata, err := DevcontainersService.GetRepoDevcontainerDetails(ctx, opts)
|
||||
hasDevContainer := err == nil && devContainerMetadata.DevContainerId > 0
|
||||
ctx.Data["HasDevContainer"] = hasDevContainer
|
||||
if hasDevContainer {
|
||||
ctx.Data["DevContainer"] = devContainerMetadata
|
||||
}
|
||||
|
||||
// 2. 检查当前仓库的当前分支是否存在有效的 /.devcontainer/devcontainer.json
|
||||
isValidRepoDevcontainerJson := isValidRepoDevcontainerJsonFile(ctx)
|
||||
if !hasDevContainer && !isValidRepoDevcontainerJson {
|
||||
ctx.Flash.Error(ctx.Tr("repo.dev_container_invalid_config_prompt"), true)
|
||||
}
|
||||
|
||||
// 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.HTML(http.StatusOK, tplGetRepoDevcontainerDetail)
|
||||
}
|
||||
207
routers/web/devcontainer/devcontainer.go
Normal file
207
routers/web/devcontainer/devcontainer.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
DevcontainersVO "code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
context "code.gitea.io/gitea/services/context"
|
||||
DevcontainersService "code.gitea.io/gitea/services/devcontainer"
|
||||
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/docker"
|
||||
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"code.gitea.io/gitea/services/devcontainer/api_services"
|
||||
"code.gitea.io/gitea/services/devcontainer/options"
|
||||
)
|
||||
|
||||
const (
|
||||
tplGetRepoDevcontainerDetail base.TplName = "repo/devcontainer/details"
|
||||
)
|
||||
|
||||
// 获取仓库 Dev Container 详细信息
|
||||
func GetRepoDevContainerDetails(ctx *context.Context) {
|
||||
|
||||
// 1. 查询当前 Repo 已有的 Dev Container 信息
|
||||
opts := &DevcontainersVO.RepoDevcontainerOptions{
|
||||
Actor: ctx.Doer,
|
||||
Repository: ctx.Repo.Repository,
|
||||
}
|
||||
//ctx.Repo.RepoLink == ctx.Repo.Repository.Link()
|
||||
devContainerMetadata, err := DevcontainersService.GetRepoDevcontainerDetails(ctx, opts)
|
||||
hasDevContainer := err == nil && devContainerMetadata.DevContainerId > 0
|
||||
ctx.Data["HasDevContainer"] = hasDevContainer
|
||||
if hasDevContainer {
|
||||
ctx.Data["DevContainer"] = devContainerMetadata
|
||||
}
|
||||
|
||||
// 2. 检查当前仓库的当前分支是否存在有效的 /.devcontainer/devcontainer.json
|
||||
isValidRepoDevcontainerJson := isValidRepoDevcontainerJsonFile(ctx)
|
||||
if !hasDevContainer && !isValidRepoDevcontainerJson {
|
||||
ctx.Flash.Error(ctx.Tr("repo.dev_container_invalid_config_prompt"), true)
|
||||
}
|
||||
// 从devcontainer.json文件提取image字段解析成仓库地址、命名空间、镜像名
|
||||
imageName := DevcontainersService.GetDefaultDevcontainerImageFromRepoDevcontainerJSON(ctx, ctx.Repo.Repository)
|
||||
registry, namespace, repo, tag := ParseImageName(imageName)
|
||||
// 获取SSH端口,没有该服务就不显示
|
||||
ctx.Data["HasWebSSH"] = false
|
||||
var port uint16
|
||||
switch setting.Devcontainer.Agent {
|
||||
case setting.KUBERNETES:
|
||||
//k8s处理
|
||||
case setting.DOCKER:
|
||||
apiRequestContext := ctx.Req.Context()
|
||||
port = docker.GetSSHPort(&apiRequestContext, devContainerMetadata.DevContainerName)
|
||||
if port != 0 {
|
||||
ctx.Data["HasWebSSH"] = true
|
||||
ctx.Data["WebSSHUrl"] = "http://" + setting.Devcontainer.Host + ":" + fmt.Sprintf("%d", port) + "/"
|
||||
}
|
||||
default:
|
||||
//默认处理
|
||||
}
|
||||
|
||||
// 3. 携带数据渲染页面,返回
|
||||
ctx.Data["RepositoryAddress"] = registry
|
||||
ctx.Data["RepositoryUsername"] = namespace
|
||||
ctx.Data["ImageName"] = ctx.Repo.Repository.Name + "-" + repo + ":" + tag
|
||||
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.HTML(http.StatusOK, tplGetRepoDevcontainerDetail)
|
||||
}
|
||||
|
||||
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 := &DevcontainersVO.CreateRepoDevcontainerOptions{
|
||||
Actor: ctx.Doer,
|
||||
Repository: ctx.Repo.Repository,
|
||||
}
|
||||
err := DevcontainersService.CreateRepoDevcontainer(ctx, opts)
|
||||
if err != nil {
|
||||
log.Error("failed to create devContainer with option{%v}: %v", opts, err.Error())
|
||||
ctx.Flash.Error(ctx.Tr("repo.dev_container_control.creation_failed_for_user", ctx.Doer.Name))
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.dev_container_control.creation_success_for_user", ctx.Doer.Name))
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 辅助判断当前用户在当前仓库是否已有 Dev Container
|
||||
func isUserDevcontainerAlreadyInRepository(ctx *context.Context) bool {
|
||||
|
||||
opts := &DevcontainersVO.RepoDevcontainerOptions{
|
||||
Actor: ctx.Doer,
|
||||
Repository: ctx.Repo.Repository,
|
||||
}
|
||||
|
||||
devcontainerDetails, _ := DevcontainersService.GetRepoDevcontainerDetails(ctx, opts)
|
||||
return devcontainerDetails.DevContainerId > 0
|
||||
}
|
||||
|
||||
func UpdateRepoDevContainerForCurrentActor(ctx *context.Context) {
|
||||
|
||||
opts1 := &DevcontainersVO.RepoDevcontainerOptions{
|
||||
Actor: ctx.Doer,
|
||||
Repository: ctx.Repo.Repository,
|
||||
}
|
||||
devContainerMetadata, _ := DevcontainersService.GetRepoDevcontainerDetails(ctx, opts1)
|
||||
|
||||
// 取得参数
|
||||
body, _ := io.ReadAll(ctx.Req.Body)
|
||||
log.Info(string(body))
|
||||
var updateInfo DevcontainersVO.UpdateInfo
|
||||
err := json.Unmarshal(body, &updateInfo)
|
||||
if err != nil {
|
||||
log.Info("UpdateRepoDevcontainer 反序列化失败:", err)
|
||||
ctx.JSON(400, map[string]string{
|
||||
"message": "error input"})
|
||||
return
|
||||
}
|
||||
opts := &options.UpdateDevcontainerOptions{
|
||||
ImageName: updateInfo.ImageName,
|
||||
PassWord: updateInfo.PassWord,
|
||||
RepositoryAddress: updateInfo.RepositoryAddress,
|
||||
RepositoryUsername: updateInfo.RepositoryUsername,
|
||||
DevContainerName: devContainerMetadata.DevContainerName,
|
||||
Actor: ctx.Doer,
|
||||
Repository: ctx.Repo.Repository,
|
||||
}
|
||||
api_services.UpdateDevcontainerAPIService(ctx, opts)
|
||||
}
|
||||
|
||||
// 删除仓库 当前用户 Dev Container
|
||||
func DeleteRepoDevContainerForCurrentActor(ctx *context.Context) {
|
||||
|
||||
if isUserDevcontainerAlreadyInRepository(ctx) {
|
||||
opts := &DevcontainersVO.RepoDevcontainerOptions{
|
||||
Actor: ctx.Doer,
|
||||
Repository: ctx.Repo.Repository,
|
||||
}
|
||||
err := DevcontainersService.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")
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/webhook"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
"github.com/emirpasic/gods/utils"
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func AddWorkflowTask(ctx *gitea_web_context.Context, cmd []string, runnerLabel string) {
|
||||
|
||||
//构建Workflow对象
|
||||
steps := make([]Step, len(cmd))
|
||||
for i, run := range cmd {
|
||||
//校验合法性、安全性, 暂无
|
||||
|
||||
steps[i] = Step{
|
||||
Name: "Step" + utils.ToString(i+1),
|
||||
Run: run,
|
||||
}
|
||||
}
|
||||
wf := Workflow{
|
||||
Name: "DevStar Workflow",
|
||||
On: On{
|
||||
Push: struct {
|
||||
Branches []string `yaml:"branches"`
|
||||
}{
|
||||
Branches: []string{"push"},
|
||||
},
|
||||
},
|
||||
Jobs: Jobs{
|
||||
CMD: struct {
|
||||
RunsOn string `yaml:"runs-on"`
|
||||
Steps []Step `yaml:"steps"`
|
||||
}{
|
||||
RunsOn: runnerLabel,
|
||||
Steps: steps,
|
||||
},
|
||||
},
|
||||
}
|
||||
// 将Workflow对象编码为YAML格式的字节流
|
||||
yamlBytes, err := yaml.Marshal(&wf)
|
||||
if err != nil {
|
||||
log.Error("Error marshaling workflow to YAML: %v", err)
|
||||
}
|
||||
branch := ctx.Repo.BranchName
|
||||
if branch == "" {
|
||||
branch = ctx.Repo.Repository.DefaultBranch
|
||||
}
|
||||
commit, _ := ctx.Repo.GitRepo.GetBranchCommit(branch)
|
||||
run := &actions_model.ActionRun{
|
||||
Title: ctx.Repo.Repository.Name, //strings.SplitN(commit.CommitMessage, "\n", 2)[0],
|
||||
RepoID: ctx.Repo.Repository.ID, //input.Repo.ID
|
||||
OwnerID: ctx.ContextUser.ID, //input.Repo.OwnerID
|
||||
WorkflowID: "DevStarInternal.yaml", //dwf.EntryName
|
||||
TriggerUserID: ctx.Doer.ID, //input.Doer.ID,
|
||||
Ref: ctx.Repo.RefName, // ref
|
||||
CommitSHA: commit.ID.String(), //commit.ID.String()
|
||||
IsForkPullRequest: false, //isForkPullRequest
|
||||
Event: webhook.HookEventPush, //input.Event
|
||||
EventPayload: string(""), //json.Marshal(input.Payload)
|
||||
TriggerEvent: "push", //dwf.TriggerEvent.Name,
|
||||
Status: actions_model.StatusWaiting,
|
||||
}
|
||||
vars := map[string]string{}
|
||||
jobs, err := jobparser.Parse(yamlBytes, jobparser.WithVars(vars))
|
||||
if err != nil {
|
||||
log.Error("jobparser.Parse: %v", err)
|
||||
}
|
||||
if err := actions_model.InsertRun(ctx, run, jobs); err != nil {
|
||||
log.Error("InsertRun: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Workflow 定义GitHub Actions的工作流结构
|
||||
type Workflow struct {
|
||||
Name string `yaml:"name"`
|
||||
On On `yaml:"on"`
|
||||
Jobs Jobs `yaml:"jobs"`
|
||||
}
|
||||
|
||||
// On 定义触发条件
|
||||
type On struct {
|
||||
Push struct {
|
||||
Branches []string `yaml:"branches"`
|
||||
} `yaml:"push"`
|
||||
}
|
||||
|
||||
// Jobs 定义工作的集合
|
||||
type Jobs struct {
|
||||
CMD struct {
|
||||
RunsOn string `yaml:"runs-on"`
|
||||
Steps []Step `yaml:"steps"`
|
||||
} `yaml:"CMD"`
|
||||
}
|
||||
|
||||
// Step 定义单个工作步骤
|
||||
type Step struct {
|
||||
Name string `yaml:"name"`
|
||||
Run string `yaml:"run"`
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
DevcontainersVO "code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
gitea_context "code.gitea.io/gitea/services/context"
|
||||
devstar_devcontainer_service "code.gitea.io/gitea/services/devstar_devcontainer"
|
||||
"net/http"
|
||||
devstar_devcontainer_service "code.gitea.io/gitea/services/devcontainer"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -1344,6 +1344,7 @@ func registerRoutes(m *web.Router) {
|
||||
m.Get("", devcontainer_web.GetRepoDevContainerDetails)
|
||||
m.Get("/create", devcontainer_web.CreateRepoDevContainer, context.RepoMustNotBeArchived()) // 仓库状态非 Archived 才可以创建 DevContainer
|
||||
m.Post("/delete", devcontainer_web.DeleteRepoDevContainerForCurrentActor)
|
||||
m.Post("/update", devcontainer_web.UpdateRepoDevContainerForCurrentActor)
|
||||
},
|
||||
// 进入 Dev Container 编辑页面需要符合条件:
|
||||
// 1. 已登录
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package devstar_devcontainer
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/docker"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/devstar_devcontainer/docker_agent"
|
||||
devcontainer_service_errors "code.gitea.io/gitea/services/devstar_devcontainer/errors"
|
||||
k8s_agent_services "code.gitea.io/gitea/services/devstar_devcontainer/k8s_agent"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devstar_devcontainer/options"
|
||||
devcontainer_service_vo "code.gitea.io/gitea/services/devstar_devcontainer/vo"
|
||||
devcontainer_service_errors "code.gitea.io/gitea/services/devcontainer/errors"
|
||||
k8s_agent_services "code.gitea.io/gitea/services/devcontainer/k8s_agent"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devcontainer/options"
|
||||
devcontainer_service_vo "code.gitea.io/gitea/services/devcontainer/vo"
|
||||
)
|
||||
|
||||
// OpenDevcontainerService 获取 DevContainer 连接信息,抽象方法,适配多种 DevContainer Agent
|
||||
@@ -22,7 +22,7 @@ func OpenDevcontainerService(ctx *gitea_web_context.Context, opts *devcontainer_
|
||||
}
|
||||
|
||||
// 1. 检查 DevContainer 功能是否开启
|
||||
if setting.Devstar.Devcontainer.Enabled == false {
|
||||
if setting.Devcontainer.Enabled == false {
|
||||
return nil, devcontainer_service_errors.ErrOperateDevcontainer{
|
||||
Action: "check availability of DevStar DevContainer",
|
||||
Message: "DevContainer is turned off globally",
|
||||
@@ -32,8 +32,8 @@ func OpenDevcontainerService(ctx *gitea_web_context.Context, opts *devcontainer_
|
||||
// 2. 根据 DevContainer Agent 类型分发任务
|
||||
apiRequestContext := ctx.Req.Context()
|
||||
openDevcontainerAbstractAgentVO := &devcontainer_service_vo.OpenDevcontainerAbstractAgentVO{}
|
||||
switch setting.Devstar.Devcontainer.Agent {
|
||||
case setting.DEVCONTAINER_AGENT_NAME_K8S:
|
||||
switch setting.Devcontainer.Agent {
|
||||
case setting.KUBERNETES:
|
||||
devcontainerApp, err := k8s_agent_services.AssignDevcontainerGetting2K8sOperator(&apiRequestContext, opts)
|
||||
if err != nil {
|
||||
return nil, devcontainer_service_errors.ErrOperateDevcontainer{
|
||||
@@ -42,8 +42,8 @@ func OpenDevcontainerService(ctx *gitea_web_context.Context, opts *devcontainer_
|
||||
}
|
||||
}
|
||||
openDevcontainerAbstractAgentVO.NodePortAssigned = devcontainerApp.Status.NodePortAssigned
|
||||
case setting.DEVCONTAINER_AGENT_NAME_DOCKER:
|
||||
port, err := docker_agent.AssignDevcontainerGettingDockerOperator(&apiRequestContext, opts)
|
||||
case setting.DOCKER:
|
||||
port, err := docker.AssignDevcontainerGettingDockerOperator(&apiRequestContext, opts)
|
||||
log.Info("port %d", port)
|
||||
if err != nil {
|
||||
return nil, devcontainer_service_errors.ErrOperateDevcontainer{
|
||||
@@ -1,24 +1,29 @@
|
||||
package devstar_devcontainer
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/services/devstar_devcontainer/docker_agent"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/docker"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
devstar_devcontainer_models "code.gitea.io/gitea/models/devstar_devcontainer"
|
||||
devstar_devcontainer_models_errors "code.gitea.io/gitea/models/devstar_devcontainer/errors"
|
||||
devcontainer_models "code.gitea.io/gitea/models/devstar_devcontainer"
|
||||
devcontainer_models_errors "code.gitea.io/gitea/models/devstar_devcontainer/errors"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
git_module "code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
DevcontainersVO "code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
devcontainer_service_dto "code.gitea.io/gitea/services/devstar_devcontainer/dto"
|
||||
devcontainer_service_errors "code.gitea.io/gitea/services/devstar_devcontainer/errors"
|
||||
devcontainer_k8s_agent_service "code.gitea.io/gitea/services/devstar_devcontainer/k8s_agent"
|
||||
devcontainer_service_dto "code.gitea.io/gitea/services/devcontainer/dto"
|
||||
devcontainer_service_errors "code.gitea.io/gitea/services/devcontainer/errors"
|
||||
devcontainer_k8s_agent_service "code.gitea.io/gitea/services/devcontainer/k8s_agent"
|
||||
"code.gitea.io/gitea/services/devstar_ssh_key_pair/api_service"
|
||||
"github.com/google/uuid"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@@ -31,7 +36,7 @@ func GetRepoDevcontainerDetails(ctx context.Context, opts *DevcontainersVO.RepoD
|
||||
|
||||
// 1. 检查参数是否有效
|
||||
if opts == nil || opts.Actor == nil || opts.Repository == nil {
|
||||
return resultRepoDevcontainerDetail, devstar_devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return resultRepoDevcontainerDetail, devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: "construct query condition for devContainer user list",
|
||||
Message: "invalid search condition",
|
||||
}
|
||||
@@ -78,7 +83,7 @@ func GetRepoDevcontainerDetails(ctx context.Context, opts *DevcontainersVO.RepoD
|
||||
|
||||
// 3. 返回
|
||||
if err != nil {
|
||||
return resultRepoDevcontainerDetail, devstar_devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return resultRepoDevcontainerDetail, devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: fmt.Sprintf("query devcontainer with repo '%v' and username '%v'", opts.Repository.Name, opts.Actor.Name),
|
||||
Message: err.Error(),
|
||||
}
|
||||
@@ -101,9 +106,9 @@ func CreateRepoDevcontainer(ctx context.Context, opts *DevcontainersVO.CreateRep
|
||||
unixTimestamp := time.Now().Unix()
|
||||
|
||||
newDevcontainer := &devcontainer_service_dto.CreateDevcontainerDTO{
|
||||
DevstarDevcontainer: devstar_devcontainer_models.DevstarDevcontainer{
|
||||
DevstarDevcontainer: devcontainer_models.DevstarDevcontainer{
|
||||
Name: getSanitizedDevcontainerName(username, repoName),
|
||||
DevcontainerHost: setting.Devstar.Devcontainer.Host,
|
||||
DevcontainerHost: setting.Devcontainer.Host,
|
||||
DevcontainerUsername: "root",
|
||||
DevcontainerWorkDir: "/data/workspace",
|
||||
RepoId: opts.Repository.ID,
|
||||
@@ -136,18 +141,26 @@ func CreateRepoDevcontainer(ctx context.Context, opts *DevcontainersVO.CreateRep
|
||||
}
|
||||
}
|
||||
newDevcontainer.SSHPublicKeyList = append(userSSHPublicKeyList, opts.SSHPublicKeyList...)
|
||||
if len(userSSHPublicKeyList) <= 0 {
|
||||
// API没提供临时SSH公钥,用户后台也没有永久SSH公钥,直接结束并回滚事务
|
||||
devstarPublicKey := getDevStarPublicKey()
|
||||
if devstarPublicKey == "" {
|
||||
return devcontainer_service_errors.ErrOperateDevcontainer{
|
||||
Action: "Check SSH Public Key List",
|
||||
Message: "禁止创建无法连通的DevContainer:用户未提供 SSH 公钥,请先使用API临时创建SSH密钥对、或在Web端手动添加SSH公钥",
|
||||
Action: fmt.Sprintf("devstar SSH Public Key Error "),
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
newDevcontainer.SSHPublicKeyList = append(newDevcontainer.SSHPublicKeyList)
|
||||
// if len(userSSHPublicKeyList) <= 0 {
|
||||
// // API没提供临时SSH公钥,用户后台也没有永久SSH公钥,直接结束并回滚事务
|
||||
// return devcontainer_service_errors.ErrOperateDevcontainer{
|
||||
// Action: "Check SSH Public Key List",
|
||||
// Message: "禁止创建无法连通的DevContainer:用户未提供 SSH 公钥,请先使用API临时创建SSH密钥对、或在Web端手动添加SSH公钥",
|
||||
// }
|
||||
// }
|
||||
|
||||
// 1. 调用 k8s Operator Agent,创建 DevContainer 资源,同时更新k8s调度器分配的 NodePort
|
||||
err = claimDevcontainerResource(&ctx, newDevcontainer)
|
||||
if err != nil {
|
||||
return devstar_devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: fmt.Sprintf("claim resource for Dev Container %v", newDevcontainer),
|
||||
Message: err.Error(),
|
||||
}
|
||||
@@ -157,12 +170,12 @@ func CreateRepoDevcontainer(ctx context.Context, opts *DevcontainersVO.CreateRep
|
||||
Table("devstar_devcontainer").
|
||||
Insert(newDevcontainer.DevstarDevcontainer)
|
||||
if err != nil {
|
||||
return devstar_devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: fmt.Sprintf("insert new DevContainer for user '%s' in repo '%s'", username, repoName),
|
||||
Message: err.Error(),
|
||||
}
|
||||
} else if rowsAffect == 0 {
|
||||
return devstar_devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: fmt.Sprintf("insert new DevContainer for user '%s' in repo '%s'", username, repoName),
|
||||
Message: "expected 1 row to be inserted, but got 0",
|
||||
}
|
||||
@@ -172,11 +185,60 @@ func CreateRepoDevcontainer(ctx context.Context, opts *DevcontainersVO.CreateRep
|
||||
|
||||
return dbTransactionErr
|
||||
}
|
||||
func getDevStarPublicKey() string {
|
||||
// 获取当前用户的主目录
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Info("Failed to get home directory: %s", err)
|
||||
}
|
||||
|
||||
// 构建公钥文件的路径
|
||||
publicKeyPath := filepath.Join(homeDir, ".ssh", "id_rsa_devstar.pub")
|
||||
privateKeyPath := filepath.Join(homeDir, ".ssh", "id_rsa_devstar")
|
||||
if !fileExists(publicKeyPath) || !fileExists(privateKeyPath) {
|
||||
err, key := api_service.GenerateNewRSASSHSessionKeyPair()
|
||||
if err != nil {
|
||||
log.Info("无法创建密钥:", err)
|
||||
return ""
|
||||
}
|
||||
// 确保~/.ssh目录存在
|
||||
sshDir := filepath.Join(homeDir, ".ssh")
|
||||
if err := os.MkdirAll(sshDir, 0700); err != nil {
|
||||
log.Info("无法创建~/.ssh目录:", err)
|
||||
return ""
|
||||
}
|
||||
// 创建密钥文件
|
||||
if err := ioutil.WriteFile(privateKeyPath, []byte(key.PrivateKeyPEM), 0600); err != nil {
|
||||
log.Info("无法写入私钥文件:", err)
|
||||
return ""
|
||||
}
|
||||
if err := ioutil.WriteFile(publicKeyPath, []byte(key.PublicKeySsh), 0600); err != nil {
|
||||
log.Info("无法写入公钥文件:", err)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
// 读取公钥文件内容
|
||||
publicKeyBytes, err := ioutil.ReadFile(publicKeyPath)
|
||||
if err != nil {
|
||||
log.Info("Failed to read public key file: %s", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
// 将文件内容转换为字符串
|
||||
return string(publicKeyBytes)
|
||||
}
|
||||
func fileExists(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
||||
// DeleteRepoDevcontainer 按照 仓库 和/或 用户信息删除 DevContainer(s)
|
||||
func DeleteRepoDevcontainer(ctx context.Context, opts *DevcontainersVO.RepoDevcontainerOptions) error {
|
||||
if ctx == nil || opts == nil || (opts.Actor == nil && opts.Repository == nil) {
|
||||
return devstar_devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: "construct query parameters",
|
||||
Message: "Invalid parameters",
|
||||
}
|
||||
@@ -190,7 +252,7 @@ func DeleteRepoDevcontainer(ctx context.Context, opts *DevcontainersVO.RepoDevco
|
||||
if opts.Repository != nil {
|
||||
sqlDevcontainerCondition = sqlDevcontainerCondition.And(builder.Eq{"repo_id": opts.Repository.ID})
|
||||
}
|
||||
var devcontainersList []devstar_devcontainer_models.DevstarDevcontainer
|
||||
var devcontainersList []devcontainer_models.DevstarDevcontainer
|
||||
|
||||
// 2. 开启事务:先获取 devcontainer列表,后删除
|
||||
dbTransactionErr := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
@@ -201,7 +263,7 @@ func DeleteRepoDevcontainer(ctx context.Context, opts *DevcontainersVO.RepoDevco
|
||||
Where(sqlDevcontainerCondition).
|
||||
Find(&devcontainersList)
|
||||
if err != nil {
|
||||
return devstar_devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: fmt.Sprintf("find devcontainer(s) with condition '%v'", sqlDevcontainerCondition),
|
||||
Message: err.Error(),
|
||||
}
|
||||
@@ -209,7 +271,7 @@ func DeleteRepoDevcontainer(ctx context.Context, opts *DevcontainersVO.RepoDevco
|
||||
|
||||
// 2.2 空列表,直接结束事务(由于前一个操作只是查询,所以回滚事务不会导致数据不一致问题)
|
||||
if len(devcontainersList) == 0 {
|
||||
return devstar_devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: fmt.Sprintf("find devcontainer(s) with condition '%v'", sqlDevcontainerCondition),
|
||||
Message: "No DevContainer found",
|
||||
}
|
||||
@@ -221,7 +283,7 @@ func DeleteRepoDevcontainer(ctx context.Context, opts *DevcontainersVO.RepoDevco
|
||||
Where(sqlDevcontainerCondition).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return devstar_devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: fmt.Sprintf("MARK devcontainer(s) as DELETED with condition '%v'", sqlDevcontainerCondition),
|
||||
Message: err.Error(),
|
||||
}
|
||||
@@ -266,24 +328,24 @@ func getSanitizedDevcontainerName(username, repoName string) string {
|
||||
}
|
||||
|
||||
// purgeDevcontainersResource 辅助函数,用于goroutine后台执行,回收DevContainer资源
|
||||
func purgeDevcontainersResource(ctx *context.Context, devcontainersList *[]devstar_devcontainer_models.DevstarDevcontainer) error {
|
||||
func purgeDevcontainersResource(ctx *context.Context, devcontainersList *[]devcontainer_models.DevstarDevcontainer) error {
|
||||
// 1. 检查 DevContainer 功能是否启用,若禁用,则直接结束,不会真正执行删除操作
|
||||
if !setting.Devstar.Devcontainer.Enabled {
|
||||
if !setting.Devcontainer.Enabled {
|
||||
// 如果用户设置禁用 DevContainer,无法删除资源,会直接忽略,而数据库相关记录会继续清空、不会发生回滚
|
||||
log.Warn("Orphan DevContainers in namespace `%s` left undeleted: %v", setting.Devstar.Devcontainer.Namespace, devcontainersList)
|
||||
log.Warn("Orphan DevContainers in namespace `%s` left undeleted: %v", setting.Devcontainer.Namespace, devcontainersList)
|
||||
return nil
|
||||
}
|
||||
// 2. 根据配置文件中指定的 DevContainer Agent 派遣创建任务
|
||||
switch setting.Devstar.Devcontainer.Agent {
|
||||
case setting.DEVCONTAINER_AGENT_NAME_K8S:
|
||||
switch setting.Devcontainer.Agent {
|
||||
case setting.KUBERNETES:
|
||||
return devcontainer_k8s_agent_service.AssignDevcontainerDeletion2K8sOperator(ctx, devcontainersList)
|
||||
case setting.DEVCONTAINER_AGENT_NAME_DOCKER:
|
||||
return docker_agent.AssignDevcontainerDeletionDockerOperator(ctx, devcontainersList)
|
||||
case setting.DOCKER:
|
||||
return docker.AssignDevcontainerDeletionDockerOperator(ctx, devcontainersList)
|
||||
default:
|
||||
// 未知 Agent,直接报错
|
||||
return devstar_devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: "dispatch DevContainer deletion",
|
||||
Message: fmt.Sprintf("unknown DevContainer agent `%s`, please check your config files", setting.Devstar.Devcontainer.Agent),
|
||||
Message: fmt.Sprintf("unknown DevContainer agent `%s`, please check your config files", setting.Devcontainer.Agent),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,25 +353,25 @@ func purgeDevcontainersResource(ctx *context.Context, devcontainersList *[]devst
|
||||
// claimDevcontainerResource 分发创建 DevContainer 任务到配置文件指定的执行器
|
||||
func claimDevcontainerResource(ctx *context.Context, newDevContainer *devcontainer_service_dto.CreateDevcontainerDTO) error {
|
||||
// 1. 检查 DevContainer 功能是否启用,若禁用,则直接结束
|
||||
if !setting.Devstar.Devcontainer.Enabled {
|
||||
return devstar_devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
if !setting.Devcontainer.Enabled {
|
||||
return devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: "Check for DevContainer functionality switch",
|
||||
Message: "DevContainer is disabled globally, please check your configuration files",
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 根据配置文件中指定的 DevContainer Agent 派遣创建任务
|
||||
switch setting.Devstar.Devcontainer.Agent {
|
||||
case setting.DEVCONTAINER_AGENT_NAME_K8S:
|
||||
switch setting.Devcontainer.Agent {
|
||||
case setting.KUBERNETES, "k8s":
|
||||
// k8s Operator
|
||||
return devcontainer_k8s_agent_service.AssignDevcontainerCreation2K8sOperator(ctx, newDevContainer)
|
||||
case setting.DEVCONTAINER_AGENT_NAME_DOCKER:
|
||||
return docker_agent.AssignDevcontainerCreationDockerOperator(ctx, newDevContainer)
|
||||
case setting.DOCKER:
|
||||
return docker.AssignDevcontainerCreationDockerOperator(ctx, newDevContainer)
|
||||
default:
|
||||
// 未知 Agent,直接报错
|
||||
return devstar_devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return devcontainer_models_errors.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: "dispatch DevContainer creation",
|
||||
Message: fmt.Sprintf("unknown DevContainer agent `%s`, please check your config files", setting.Devstar.Devcontainer.Agent),
|
||||
Message: fmt.Sprintf("unknown DevContainer agent `%s`, please check your config files", setting.Devcontainer.Agent),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -320,14 +382,14 @@ func GetDefaultDevcontainerImageFromRepoDevcontainerJSON(ctx context.Context, re
|
||||
// 1. 获取默认分支名
|
||||
branchName := repo.DefaultBranch
|
||||
if len(branchName) == 0 {
|
||||
branchName = setting.Devstar.Devcontainer.DefaultGitBranchName
|
||||
branchName = setting.Devcontainer.DefaultGitBranchName
|
||||
}
|
||||
|
||||
// 2. 打开默认分支
|
||||
gitRepoEntity, err := git_module.OpenRepository(ctx, repo.RepoPath())
|
||||
if err != nil {
|
||||
log.Error("Failed to open repository %s: %v", repo.RepoPath(), err)
|
||||
return setting.Devstar.Devcontainer.DefaultDevcontainerImageName
|
||||
return setting.Devcontainer.DefaultDevcontainerImageName
|
||||
}
|
||||
defer func(gitRepoEntity *git_module.Repository) {
|
||||
_ = gitRepoEntity.Close()
|
||||
@@ -336,7 +398,7 @@ func GetDefaultDevcontainerImageFromRepoDevcontainerJSON(ctx context.Context, re
|
||||
// 3. 获取分支名称
|
||||
commit, err := gitRepoEntity.GetBranchCommit(branchName)
|
||||
if err != nil {
|
||||
return setting.Devstar.Devcontainer.DefaultDevcontainerImageName
|
||||
return setting.Devcontainer.DefaultDevcontainerImageName
|
||||
}
|
||||
|
||||
// 4. 读取 .devcontainer/devcontainer.json 文件
|
||||
@@ -344,19 +406,19 @@ func GetDefaultDevcontainerImageFromRepoDevcontainerJSON(ctx context.Context, re
|
||||
devcontainerJSONContent, err := commit.GetFileContent(".devcontainer/devcontainer.json", maxDevcontainerJSONSize)
|
||||
if err != nil {
|
||||
log.Error("Failed to get .devcontainer/devcontainer.json file: %v", err)
|
||||
return setting.Devstar.Devcontainer.DefaultDevcontainerImageName
|
||||
return setting.Devcontainer.DefaultDevcontainerImageName
|
||||
}
|
||||
|
||||
// 5. 解析 JSON
|
||||
devContainerJSON, err := devstar_devcontainer_models.Unmarshal(devcontainerJSONContent)
|
||||
devContainerJSON, err := devcontainer_models.Unmarshal(devcontainerJSONContent)
|
||||
if err != nil {
|
||||
log.Error("Failed to unmarshal .devcontainer/devcontainer.json: %v", err)
|
||||
return setting.Devstar.Devcontainer.DefaultDevcontainerImageName
|
||||
return setting.Devcontainer.DefaultDevcontainerImageName
|
||||
}
|
||||
|
||||
// 6. 解析并返回
|
||||
if len(devContainerJSON.Image) == 0 {
|
||||
return setting.Devstar.Devcontainer.DefaultDevcontainerImageName
|
||||
return setting.Devcontainer.DefaultDevcontainerImageName
|
||||
}
|
||||
return devContainerJSON.Image
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package devstar_devcontainer
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
devstar_devcontainer_models "code.gitea.io/gitea/models/devstar_devcontainer/errors"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
devcontainer_models "code.gitea.io/gitea/models/devstar_devcontainer/errors"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
@@ -24,7 +25,7 @@ func GetUserDevcontainersList(ctx context.Context, opts *vo.SearchUserDevcontain
|
||||
|
||||
// 1. 查询参数预处理
|
||||
if opts == nil || opts.Actor == nil {
|
||||
return resultDevContainerListVO, devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return resultDevContainerListVO, devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: "construct query condition for devContainer user list",
|
||||
Message: "invalid search condition",
|
||||
}
|
||||
@@ -63,7 +64,7 @@ func GetUserDevcontainersList(ctx context.Context, opts *vo.SearchUserDevcontain
|
||||
Where(sqlCondition).
|
||||
Count()
|
||||
if err != nil {
|
||||
return devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: "count devContainer item numbers",
|
||||
Message: err.Error(),
|
||||
}
|
||||
@@ -121,7 +122,7 @@ func GetUserDevcontainersList(ctx context.Context, opts *vo.SearchUserDevcontain
|
||||
Find(&resultDevContainerListVO.DevContainers)
|
||||
if err != nil {
|
||||
// 查询出错,返回错误信息,结束事务
|
||||
return devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
return devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: fmt.Sprintf("list devContainer for user '%v' at page %v", opts.Actor.Name, opts.PaginationOptions.Page),
|
||||
Message: err.Error(),
|
||||
}
|
||||
@@ -133,7 +134,7 @@ func GetUserDevcontainersList(ctx context.Context, opts *vo.SearchUserDevcontain
|
||||
|
||||
if errDbTransaction != nil {
|
||||
return resultDevContainerListVO,
|
||||
devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
Action: "query user DevContainer List",
|
||||
Message: errDbTransaction.Error(),
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
package api_services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/repo"
|
||||
DevcontainersVO "code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
DevcontainersService "code.gitea.io/gitea/services/devstar_devcontainer"
|
||||
devcontainer_service_errors "code.gitea.io/gitea/services/devstar_devcontainer/errors"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devstar_devcontainer/options"
|
||||
"context"
|
||||
"regexp"
|
||||
DevcontainersService "code.gitea.io/gitea/services/devcontainer"
|
||||
devcontainer_service_errors "code.gitea.io/gitea/services/devcontainer/errors"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devcontainer/options"
|
||||
)
|
||||
|
||||
// CreateDevcontainerAPIService API专用创建 DevContainer Service
|
||||
@@ -1,14 +1,15 @@
|
||||
package api_services
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/repo"
|
||||
DevcontainersVO "code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
DevcontainersService "code.gitea.io/gitea/services/devstar_devcontainer"
|
||||
devcontainer_service_errors "code.gitea.io/gitea/services/devstar_devcontainer/errors"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devstar_devcontainer/options"
|
||||
"context"
|
||||
DevcontainersService "code.gitea.io/gitea/services/devcontainer"
|
||||
devcontainer_service_errors "code.gitea.io/gitea/services/devcontainer/errors"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devcontainer/options"
|
||||
)
|
||||
|
||||
// DeleteDevcontainerAPIService API 专用删除 DevContainer Service
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"code.gitea.io/gitea/models/repo"
|
||||
DevcontainersVO "code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
DevcontainersService "code.gitea.io/gitea/services/devstar_devcontainer"
|
||||
devcontainer_service_errors "code.gitea.io/gitea/services/devstar_devcontainer/errors"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devstar_devcontainer/options"
|
||||
DevcontainersService "code.gitea.io/gitea/services/devcontainer"
|
||||
devcontainer_service_errors "code.gitea.io/gitea/services/devcontainer/errors"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devcontainer/options"
|
||||
)
|
||||
|
||||
// OpenDevcontainerAPIService API 专用获取 DevContainer Service
|
||||
@@ -55,9 +55,10 @@ func OpenDevcontainerAPIService(ctx *gitea_web_context.Context, opts *devcontain
|
||||
|
||||
// 2. 调用抽象层获取 DevContainer 最新状态(需要根据用户传入的 wait 参数决定是否要阻塞等待 DevContainer 就绪)
|
||||
optsOpenDevcontainer := &devcontainer_service_options.OpenDevcontainerAppDispatcherOptions{
|
||||
Name: devcontainerDetails.DevContainerName,
|
||||
Port: devcontainerDetails.DevContainerPort,
|
||||
Wait: opts.Wait,
|
||||
Name: devcontainerDetails.DevContainerName,
|
||||
Port: devcontainerDetails.DevContainerPort,
|
||||
Wait: opts.Wait,
|
||||
UserPublicKey: opts.UserPublicKey,
|
||||
}
|
||||
|
||||
openDevcontainerAbstractAgentVO, err := DevcontainersService.OpenDevcontainerService(ctx, optsOpenDevcontainer)
|
||||
@@ -0,0 +1,19 @@
|
||||
package api_services
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/docker"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devcontainer/options"
|
||||
)
|
||||
|
||||
func UpdateDevcontainerAPIService(ctx *gitea_web_context.Context, opts *devcontainer_service_options.UpdateDevcontainerOptions) {
|
||||
switch setting.Devcontainer.Agent {
|
||||
case setting.KUBERNETES:
|
||||
//k8s处理
|
||||
case setting.DOCKER:
|
||||
docker.AssignDevcontainerUpdateDockerOperator(ctx, opts)
|
||||
default:
|
||||
//默认处理
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
devcontainer_dto "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/dto"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
devcontainer_service_dto "code.gitea.io/gitea/services/devstar_devcontainer/dto"
|
||||
devcontainer_service_dto "code.gitea.io/gitea/services/devcontainer/dto"
|
||||
apimachinery_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
devcontainer_k8s_agent_module "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent"
|
||||
@@ -31,7 +31,7 @@ func AssignDevcontainerCreation2K8sOperator(ctx *context.Context, newDevContaine
|
||||
opts := &devcontainer_dto.CreateDevcontainerOptions{
|
||||
CreateOptions: apimachinery_meta_v1.CreateOptions{},
|
||||
Name: newDevContainer.Name,
|
||||
Namespace: setting.Devstar.Devcontainer.Namespace,
|
||||
Namespace: setting.Devcontainer.Namespace,
|
||||
Image: newDevContainer.Image,
|
||||
/**
|
||||
* 配置 Kubernetes 主容器启动命令注意事项:
|
||||
@@ -1,6 +1,9 @@
|
||||
package k8s_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
devstar_devcontainer_models "code.gitea.io/gitea/models/devstar_devcontainer"
|
||||
devcontainer_k8s_agent_module "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent"
|
||||
devcontainer_dto "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/dto"
|
||||
@@ -8,8 +11,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/devstar_cloud_provider"
|
||||
"context"
|
||||
"fmt"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
@@ -29,7 +30,7 @@ func AssignDevcontainerDeletion2K8sOperator(ctx *context.Context, devcontainersL
|
||||
// 2. 调用 modules 层 k8s Agent,执行删除资源
|
||||
opts := &devcontainer_dto.DeleteDevcontainerOptions{
|
||||
DeleteOptions: metav1.DeleteOptions{},
|
||||
Namespace: setting.Devstar.Devcontainer.Namespace,
|
||||
Namespace: setting.Devcontainer.Namespace,
|
||||
}
|
||||
if devcontainersList == nil || len(*devcontainersList) == 0 {
|
||||
return devcontainer_errors.ErrOperateDevcontainer{
|
||||
@@ -1,15 +1,16 @@
|
||||
package k8s_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
devcontainer_k8s_agent_module "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent"
|
||||
devcontainer_api_v1 "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/api/v1"
|
||||
devcontainer_dto "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/dto"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/devstar_devcontainer/errors"
|
||||
devcontainer_k8s_agent_errors "code.gitea.io/gitea/services/devstar_devcontainer/k8s_agent/errors"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devstar_devcontainer/options"
|
||||
"context"
|
||||
"fmt"
|
||||
"code.gitea.io/gitea/services/devcontainer/errors"
|
||||
devcontainer_k8s_agent_errors "code.gitea.io/gitea/services/devcontainer/k8s_agent/errors"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devcontainer/options"
|
||||
apimachinery_api_metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
@@ -37,7 +38,7 @@ func AssignDevcontainerGetting2K8sOperator(ctx *context.Context, opts *devcontai
|
||||
optsGetDevcontainer := &devcontainer_dto.GetDevcontainerOptions{
|
||||
GetOptions: apimachinery_api_metav1.GetOptions{},
|
||||
Name: opts.Name,
|
||||
Namespace: setting.Devstar.Devcontainer.Namespace,
|
||||
Namespace: setting.Devcontainer.Namespace,
|
||||
Wait: opts.Wait,
|
||||
}
|
||||
devcontainerApp, err := devcontainer_k8s_agent_module.GetDevcontainer(ctx, client, optsGetDevcontainer)
|
||||
@@ -0,0 +1,11 @@
|
||||
package options
|
||||
|
||||
import user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
// AbstractOpenDevcontainerOptions 封装 API 获取 DevContainer 数据,即 router 层 GET /api/devcontainer 向下传递的数据结构
|
||||
type AbstractOpenDevcontainerOptions struct {
|
||||
Wait bool // 标记用户在API调用时候是否希望阻塞等待 DevContainer 就绪
|
||||
Actor *user_model.User // 当前操作用户实体
|
||||
RepoId int64 // 仓库ID,用于 API Service 层查询数据库
|
||||
UserPublicKey string
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package options
|
||||
|
||||
type OpenDevcontainerAppDispatcherOptions struct {
|
||||
Name string `json:"name"`
|
||||
Wait bool `json:"wait"`
|
||||
Port uint16
|
||||
UserPublicKey string
|
||||
}
|
||||
16
services/devcontainer/options/UpdateDevcontainerOptions.go
Normal file
16
services/devcontainer/options/UpdateDevcontainerOptions.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
)
|
||||
|
||||
type UpdateDevcontainerOptions struct {
|
||||
ImageName string
|
||||
PassWord string
|
||||
RepositoryAddress string
|
||||
RepositoryUsername string
|
||||
DevContainerName string
|
||||
Actor *user_model.User
|
||||
Repository *repo_model.Repository
|
||||
}
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
// CreateNATRulePort 抽象接口,创建 NAT 端口映射规则
|
||||
func CreateNATRulePort(privatePort, publicPort uint64, description string) error {
|
||||
|
||||
if setting.Devstar.Cloud.Enabled == false {
|
||||
if setting.Cloud.Enabled == false {
|
||||
return devstar_cloud_provider_errors.ErrCloudNATProviderDisabled{}
|
||||
}
|
||||
|
||||
// 根据配置文件指定云服务厂商创建 NAT Rule
|
||||
switch setting.Devstar.Cloud.Provider {
|
||||
switch setting.Cloud.Provider {
|
||||
case setting.CLOUD_PROVIDER_TENCENT:
|
||||
// 指定腾讯云执行 NAT 端口创建
|
||||
return devstar_cloud_provider_tencent_nat.AssignDevstarCloudNATPortForwarding2TencentCloud(privatePort, publicPort, description)
|
||||
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
// DeleteNATRulePort 抽象接口,根据 NAT 端口映射规则列表描述需要删除的信息
|
||||
func DeleteNATRulePort(description string) error {
|
||||
|
||||
if setting.Devstar.Cloud.Enabled == false {
|
||||
if setting.Cloud.Enabled == false {
|
||||
return devstar_cloud_provider_errors.ErrCloudNATProviderDisabled{}
|
||||
}
|
||||
|
||||
// 根据配置文件指定云服务厂商创建 NAT Rule
|
||||
switch setting.Devstar.Cloud.Provider {
|
||||
switch setting.Cloud.Provider {
|
||||
case setting.CLOUD_PROVIDER_TENCENT:
|
||||
// 指定腾讯云执行 NAT 端口删除
|
||||
return devstar_cloud_provider_tencent_nat.AssignDeletingDevstarCloudNATPortForwarding2TencentCloud(description)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package nat_port_mapping
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
devstar_cloud_provider_service_errors "code.gitea.io/gitea/services/devstar_cloud_provider/errors"
|
||||
"fmt"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
vpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312"
|
||||
)
|
||||
@@ -17,20 +18,20 @@ func AssignDevstarCloudNATPortForwarding2TencentCloud(privatePort, publicPort ui
|
||||
client, err := GetTencentNATRuleClient()
|
||||
if err != nil {
|
||||
return devstar_cloud_provider_service_errors.ErrFailed2GetNATClient{
|
||||
CloudProviderName: setting.Devstar.Cloud.Provider,
|
||||
CloudProviderName: setting.Cloud.Provider,
|
||||
Reason: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 实例化 request 请求对象
|
||||
request := vpc.NewCreateNatGatewayDestinationIpPortTranslationNatRuleRequest()
|
||||
request.NatGatewayId = common.StringPtr(setting.Devstar.Cloud.Tencent.NatGatewayId)
|
||||
request.NatGatewayId = common.StringPtr(setting.Cloud.Tencent.NatGatewayId)
|
||||
request.DestinationIpPortTranslationNatRules = []*vpc.DestinationIpPortTranslationNatRule{
|
||||
&vpc.DestinationIpPortTranslationNatRule{
|
||||
IpProtocol: common.StringPtr(setting.Devstar.Cloud.Tencent.IpProtocol),
|
||||
PublicIpAddress: common.StringPtr(setting.Devstar.Cloud.Tencent.PublicIpAddress),
|
||||
IpProtocol: common.StringPtr(setting.Cloud.Tencent.IpProtocol),
|
||||
PublicIpAddress: common.StringPtr(setting.Cloud.Tencent.PublicIpAddress),
|
||||
PublicPort: common.Uint64Ptr(publicPort),
|
||||
PrivateIpAddress: common.StringPtr(setting.Devstar.Cloud.Tencent.PrivateIpAddress),
|
||||
PrivateIpAddress: common.StringPtr(setting.Cloud.Tencent.PrivateIpAddress),
|
||||
PrivatePort: common.Uint64Ptr(privatePort),
|
||||
Description: common.StringPtr(portDescription),
|
||||
},
|
||||
@@ -64,7 +65,7 @@ func AssignDeletingDevstarCloudNATPortForwarding2TencentCloud(portDescription st
|
||||
client, err := GetTencentNATRuleClient()
|
||||
if err != nil {
|
||||
err = devstar_cloud_provider_service_errors.ErrFailed2GetNATClient{
|
||||
CloudProviderName: setting.Devstar.Cloud.Provider,
|
||||
CloudProviderName: setting.Cloud.Provider,
|
||||
Reason: err.Error(),
|
||||
}
|
||||
return devstar_cloud_provider_service_errors.ErrFailed2DeleteNatRules{
|
||||
@@ -101,7 +102,7 @@ func GetDevstarCloudTencentNATPortForwarding(client *vpc.Client, portDescription
|
||||
request.Filters = []*vpc.Filter{
|
||||
&vpc.Filter{
|
||||
Name: common.StringPtr("nat-gateway-id"),
|
||||
Values: []*string{common.StringPtr(setting.Devstar.Cloud.Tencent.NatGatewayId)},
|
||||
Values: []*string{common.StringPtr(setting.Cloud.Tencent.NatGatewayId)},
|
||||
},
|
||||
&vpc.Filter{
|
||||
Name: common.StringPtr("description"),
|
||||
@@ -126,7 +127,7 @@ func GetDevstarCloudTencentNATPortForwarding(client *vpc.Client, portDescription
|
||||
// DeleteDevstarCloudTencentNATPortForwardingRuleSet 删除 NAT Result Set 中的规则()
|
||||
func DeleteDevstarCloudTencentNATPortForwardingRuleSet(client *vpc.Client, natRuleSet []*vpc.NatGatewayDestinationIpPortTranslationNatRule) (response *vpc.DeleteNatGatewayDestinationIpPortTranslationNatRuleResponse, err error) {
|
||||
request := vpc.NewDeleteNatGatewayDestinationIpPortTranslationNatRuleRequest()
|
||||
request.NatGatewayId = common.StringPtr(setting.Devstar.Cloud.Tencent.NatGatewayId)
|
||||
request.NatGatewayId = common.StringPtr(setting.Cloud.Tencent.NatGatewayId)
|
||||
request.DestinationIpPortTranslationNatRules = []*vpc.DestinationIpPortTranslationNatRule{}
|
||||
for _, natRule := range natRuleSet {
|
||||
request.DestinationIpPortTranslationNatRules = append(request.DestinationIpPortTranslationNatRules, &vpc.DestinationIpPortTranslationNatRule{
|
||||
|
||||
@@ -14,13 +14,13 @@ func GetTencentNATRuleClient() (client *vpc.Client, err error) {
|
||||
// 以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305
|
||||
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
|
||||
credential := common.NewCredential(
|
||||
setting.Devstar.Cloud.Tencent.SecretId,
|
||||
setting.Devstar.Cloud.Tencent.SecretKey,
|
||||
setting.Cloud.Tencent.SecretId,
|
||||
setting.Cloud.Tencent.SecretKey,
|
||||
)
|
||||
|
||||
// 实例化一个client选项,可选的,没有特殊需求可以跳过
|
||||
cpf := profile.NewClientProfile()
|
||||
cpf.HttpProfile.Endpoint = setting.Devstar.Cloud.Tencent.Endpoint
|
||||
cpf.HttpProfile.Endpoint = setting.Cloud.Tencent.Endpoint
|
||||
// 实例化要请求产品的client对象,clientProfile是可选的
|
||||
return vpc.NewClient(credential, setting.Devstar.Cloud.Tencent.Region, cpf)
|
||||
return vpc.NewClient(credential, setting.Cloud.Tencent.Region, cpf)
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package docker_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
devcontainer_service_errors "code.gitea.io/gitea/services/devstar_devcontainer/errors"
|
||||
devcontainer_service_options "code.gitea.io/gitea/services/devstar_devcontainer/options"
|
||||
)
|
||||
|
||||
func AssignDevcontainerGettingDockerOperator(ctx *context.Context, opts *devcontainer_service_options.OpenDevcontainerAppDispatcherOptions) (uint16, error) {
|
||||
// 1. 创建docker client
|
||||
cli, err := CreateDockerClient(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if cli != nil {
|
||||
defer cli.Close()
|
||||
}
|
||||
|
||||
// 获取容器详细信息
|
||||
containerJSON, err := cli.ContainerInspect(context.Background(), opts.Name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 获取端口映射信息
|
||||
portBindings := containerJSON.NetworkSettings.Ports
|
||||
for containerPort, bindings := range portBindings {
|
||||
for _, binding := range bindings {
|
||||
log.Info("Container Port %s is mapped to Host Port %s on IP %s\n", containerPort, binding.HostPort, binding.HostIP)
|
||||
if containerPort.Port() == "22" {
|
||||
v, err := strconv.ParseUint(binding.HostPort, 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint16(v), nil
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return 0, devcontainer_service_errors.ErrOperateDevcontainer{
|
||||
Action: "Open DevContainer in docker",
|
||||
Message: "cannot find SSH containerPort 22 for DevContainer " + opts.Name,
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package options
|
||||
|
||||
import user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
// AbstractOpenDevcontainerOptions 封装 API 获取 DevContainer 数据,即 router 层 GET /api/devcontainer 向下传递的数据结构
|
||||
type AbstractOpenDevcontainerOptions struct {
|
||||
Wait bool // 标记用户在API调用时候是否希望阻塞等待 DevContainer 就绪
|
||||
Actor *user_model.User // 当前操作用户实体
|
||||
RepoId int64 // 仓库ID,用于 API Service 层查询数据库
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package options
|
||||
|
||||
type OpenDevcontainerAppDispatcherOptions struct {
|
||||
Name string `json:"name"`
|
||||
Wait bool `json:"wait"`
|
||||
Port uint16
|
||||
}
|
||||
@@ -1,22 +1,23 @@
|
||||
package api_service
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/devstar_ssh_key_pair/errors"
|
||||
"code.gitea.io/gitea/services/devstar_ssh_key_pair/vo"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/devstar_ssh_key_pair/errors"
|
||||
"code.gitea.io/gitea/services/devstar_ssh_key_pair/vo"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// GenerateNewRSASSHSessionKeyPair 生成 RSA SSH 密钥对
|
||||
func GenerateNewRSASSHSessionKeyPair() (error, *vo.GenerateNewRSASSHSessionKeyPairVO) {
|
||||
|
||||
// 1. 生成 SSH 密钥对 (算法 RSA,长度 setting.Devstar.SSHKeypair.KeySize)
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, setting.Devstar.SSHKeypair.KeySize)
|
||||
// 1. 生成 SSH 密钥对 (算法 RSA,长度 setting.SSHKeypair.KeySize)
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, setting.SSHKeypair.KeySize)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
}
|
||||
@@ -73,6 +74,6 @@ func GenerateNewRSASSHSessionKeyPair() (error, *vo.GenerateNewRSASSHSessionKeyPa
|
||||
PublicKeyPEM: publicKeyPemStr,
|
||||
PrivateKeyPEM: privateKeyPemStr,
|
||||
PublicKeySsh: sshPublicKeyStr,
|
||||
KeySize: strconv.Itoa(setting.Devstar.SSHKeypair.KeySize),
|
||||
KeySize: strconv.Itoa(setting.SSHKeypair.KeySize),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
DevcontainersVO "code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
DevcontainersService "code.gitea.io/gitea/services/devstar_devcontainer"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
DevcontainersVO "code.gitea.io/gitea/routers/api/devcontainer/vo"
|
||||
DevcontainersService "code.gitea.io/gitea/services/devcontainer"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
|
||||
@@ -61,6 +61,9 @@
|
||||
</table>
|
||||
<p>*See GET /api/devcontainer with param 'repoId' and 'wait'</p>
|
||||
{{end}}
|
||||
<!-- {{if .HasWebSSH}}
|
||||
<iframe src="{{.WebSSHUrl}}" style="width: 100%"></iframe>
|
||||
{{end}}-->
|
||||
</div>
|
||||
<!-- 结束:Dev Container 正文内容 - 左侧主展示区 -->
|
||||
|
||||
@@ -74,6 +77,10 @@
|
||||
<div class="item">{{svg "octicon-person" 16 "tw-mr-2"}} <a href="{{.ContextUser.HomeLink}}">{{.ContextUser.Name}}</a></div>
|
||||
{{if .HasDevContainer}}
|
||||
<div class="item"><a class="delete-button flex-text-inline" data-modal="#delete-repo-devcontainer-of-user-modal" href="#" data-url="{{.Repository.Link}}/dev-container/delete">{{svg "octicon-trash" 14}}{{ctx.Locale.Tr "repo.dev_container_control.delete"}}</a></div>
|
||||
<div class="item"><a class="delete-button flex-text-inline" style="color:black;" data-modal-id="updatemodal" href="#">{{svg "octicon-database"}}{{ctx.Locale.Tr "repo.dev_container_control.update"}}</a></div>
|
||||
<div class="item"><a class="flex-text-inline" style="color:black;" href="{{.WebSSHUrl}}" target="_blank">{{svg "octicon-code" 14}}open with WebTerminal</a></div>
|
||||
<div class="item"><a class="flex-text-inline" style="color:black;" href="vscode://devstar-open?repo=GIT-URL" target="_blank">{{svg "octicon-code" 14}}open with VSCode</a></div>
|
||||
|
||||
{{else if .HasValidDevContainerJSON}}
|
||||
<div class="item">
|
||||
<a class="button flex-text-inline" href="{{.Repository.Link}}/dev-container/create">{{svg "octicon-terminal" 14 "tw-mr-2"}} {{ctx.Locale.Tr "repo.dev_container_control.create"}}</a>
|
||||
@@ -101,5 +108,74 @@
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
<!-- 确认 Dev Container 模态对话框 -->
|
||||
<div class="ui g-modal-confirm delete modal" style="width: 35%" id="updatemodal">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.dev_container_control.update"}}
|
||||
</div>
|
||||
<script>
|
||||
function submitForm(event) {
|
||||
event.preventDefault(); // 阻止默认的表单提交行为
|
||||
const {csrfToken} = window.config;
|
||||
const {appSubUrl} = window.config;
|
||||
const form = document.getElementById('updateForm');
|
||||
const submitButton = document.getElementById('updateSubmitButton');
|
||||
const closeButton = document.getElementById('updateCloseButton');
|
||||
submitButton.disabled = true;
|
||||
const formData = new FormData(form);
|
||||
fetch('{{.Repository.Link}}'+'/dev-container/update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-csrf-token': csrfToken, // 如果需要认证
|
||||
'content-type' : 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
RepositoryAddress: formData.get('RepositoryAddress'),
|
||||
RepositoryUsername: formData.get('RepositoryUsername'),
|
||||
RepositoryPassword: formData.get('RepositoryPassword'),
|
||||
ImageName: formData.get('ImageName'),
|
||||
})
|
||||
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
submitButton.disabled = false;
|
||||
alert(data.message);
|
||||
if(data.redirect){
|
||||
closeButton.click()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
alert('提交失败,请重试。');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<div class="content">
|
||||
<form class="ui form tw-max-w-2xl tw-m-auto" id="updateForm" onsubmit="submitForm(event)">
|
||||
<div class="required field ">
|
||||
<label for="RepositoryAddress">Registry:</label>
|
||||
<input style="border: 1px solid black;" type="text" id="RepositoryAddress" name="RepositoryAddress" value="{{.RepositoryAddress}}">
|
||||
</div>
|
||||
<div class="required field ">
|
||||
<label for="RepositoryUsername">Registry Username:</label>
|
||||
<input style="border: 1px solid black;" type="text" id="RepositoryUsername" name="RepositoryUsername" value="{{.RepositoryUsername}}">
|
||||
</div>
|
||||
<div class="required field ">
|
||||
<label for="RepositoryPassword">Registry Password:</label>
|
||||
<input style="border: 1px solid black;" type="text" id="RepositoryPassword" name="RepositoryPassword" required>
|
||||
</div>
|
||||
<div class="required field ">
|
||||
<label for="ImageName">Image(name:tag):</label>
|
||||
<input style="border: 1px solid black;" type="text" id="ImageName" name="ImageName" value="{{.ImageName}}">
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="ui primary button" type="submit" id="updateSubmitButton" >Submit</button>
|
||||
<button class="ui cancel button" id="updateCloseButton">Close</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
|
||||
Reference in New Issue
Block a user