diff --git a/docs/content/installation/on-kubernetes.zh-cn.md b/docs/content/installation/on-kubernetes.zh-cn.md index 0685131eac..393af7ab6a 100644 --- a/docs/content/installation/on-kubernetes.zh-cn.md +++ b/docs/content/installation/on-kubernetes.zh-cn.md @@ -55,17 +55,17 @@ data: ui.admin: | DEV_CONTAINERS_PAGING_NUM = 50 - devstar.devcontainer: | + devcontainer: | ENABLED = true AGENT = k8s TIMEOUT_SECONDS = 120 HOST = NAMESPACE = - 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 = 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 > } > } diff --git a/modules/devstar_devcontainer/k8s_agent/CreateDevcontainer.go b/modules/devstar_devcontainer/k8s_agent/CreateDevcontainer.go index 33e9c7880e..b9494a312b 100644 --- a/modules/devstar_devcontainer/k8s_agent/CreateDevcontainer.go +++ b/modules/devstar_devcontainer/k8s_agent/CreateDevcontainer.go @@ -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) diff --git a/modules/devstar_devcontainer/k8s_agent/GetDevcontainer.go b/modules/devstar_devcontainer/k8s_agent/GetDevcontainer.go index f74c3bdbb6..22d2118f57 100644 --- a/modules/devstar_devcontainer/k8s_agent/GetDevcontainer.go +++ b/modules/devstar_devcontainer/k8s_agent/GetDevcontainer.go @@ -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, } } diff --git a/modules/devstar_devcontainer/k8s_agent/init.go b/modules/devstar_devcontainer/k8s_agent/init.go index 8564b38d90..b60997b87f 100644 --- a/modules/devstar_devcontainer/k8s_agent/init.go +++ b/modules/devstar_devcontainer/k8s_agent/init.go @@ -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 } } diff --git a/services/devstar_devcontainer/docker_agent/AssignDevcontainerCreationDockerOperator.go b/modules/docker/AssignDevcontainerCreationDockerOperator.go similarity index 90% rename from services/devstar_devcontainer/docker_agent/AssignDevcontainerCreationDockerOperator.go rename to modules/docker/AssignDevcontainerCreationDockerOperator.go index 792c305dd3..c2cce24d27 100644 --- a/services/devstar_devcontainer/docker_agent/AssignDevcontainerCreationDockerOperator.go +++ b/modules/docker/AssignDevcontainerCreationDockerOperator.go @@ -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) // 获取容器详细信息 diff --git a/services/devstar_devcontainer/docker_agent/AssignDevcontainerDeletionDockerOperator.go b/modules/docker/AssignDevcontainerDeletionDockerOperator.go similarity index 98% rename from services/devstar_devcontainer/docker_agent/AssignDevcontainerDeletionDockerOperator.go rename to modules/docker/AssignDevcontainerDeletionDockerOperator.go index 346a9a57c7..2fe440c3b8 100644 --- a/services/devstar_devcontainer/docker_agent/AssignDevcontainerDeletionDockerOperator.go +++ b/modules/docker/AssignDevcontainerDeletionDockerOperator.go @@ -1,4 +1,4 @@ -package docker_agent +package docker import ( devstar_devcontainer_models "code.gitea.io/gitea/models/devstar_devcontainer" diff --git a/modules/docker/AssignDevcontainerGettingDockerOperator.go b/modules/docker/AssignDevcontainerGettingDockerOperator.go new file mode 100644 index 0000000000..a7bb87deec --- /dev/null +++ b/modules/docker/AssignDevcontainerGettingDockerOperator.go @@ -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() { + +} diff --git a/modules/docker/AssignDevcontainerUpdateDockerOperator.go b/modules/docker/AssignDevcontainerUpdateDockerOperator.go new file mode 100644 index 0000000000..c2d18c4232 --- /dev/null +++ b/modules/docker/AssignDevcontainerUpdateDockerOperator.go @@ -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"}) +} diff --git a/services/devstar_devcontainer/docker_agent/DockerUtils.go b/modules/docker/DockerUtils.go similarity index 63% rename from services/devstar_devcontainer/docker_agent/DockerUtils.go rename to modules/docker/DockerUtils.go index 8fd742c4af..cb41e790a3 100644 --- a/services/devstar_devcontainer/docker_agent/DockerUtils.go +++ b/modules/docker/DockerUtils.go @@ -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 +} diff --git a/modules/setting/devcontainer.go b/modules/setting/devcontainer.go new file mode 100644 index 0000000000..ce85d2885c --- /dev/null +++ b/modules/setting/devcontainer.go @@ -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() + } +} diff --git a/modules/setting/devstar_devcontainer.go b/modules/setting/devstar_devcontainer.go deleted file mode 100644 index b75a7b392a..0000000000 --- a/modules/setting/devstar_devcontainer.go +++ /dev/null @@ -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() -} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index f111cf8232..ca345147ec 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -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) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 91e227499a..6b1185e856 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -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'. diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 4bed8cd610..5c34608201 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -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' 开发容器创建失败 diff --git a/routers/api/devcontainer/create_repo_devcontainer.go b/routers/api/devcontainer/create_repo_devcontainer.go index b0493cbbeb..ee0f883a36 100644 --- a/routers/api/devcontainer/create_repo_devcontainer.go +++ b/routers/api/devcontainer/create_repo_devcontainer.go @@ -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 diff --git a/routers/api/devcontainer/delete_repo_devcontainer.go b/routers/api/devcontainer/delete_repo_devcontainer.go index 4e660ef691..d89de3fbe5 100644 --- a/routers/api/devcontainer/delete_repo_devcontainer.go +++ b/routers/api/devcontainer/delete_repo_devcontainer.go @@ -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 diff --git a/routers/api/devcontainer/get_repo_devcontainer.go b/routers/api/devcontainer/get_repo_devcontainer.go index 0b5dc8259d..2c6d290b4a 100644 --- a/routers/api/devcontainer/get_repo_devcontainer.go +++ b/routers/api/devcontainer/get_repo_devcontainer.go @@ -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 { diff --git a/routers/api/devcontainer/list_user_devcontainers.go b/routers/api/devcontainer/list_user_devcontainers.go index aff2772c0f..b9b1158973 100644 --- a/routers/api/devcontainer/list_user_devcontainers.go +++ b/routers/api/devcontainer/list_user_devcontainers.go @@ -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 diff --git a/routers/api/devcontainer/vo/UpdateRepoDevcontainerVO.go b/routers/api/devcontainer/vo/UpdateRepoDevcontainerVO.go new file mode 100644 index 0000000000..fa6a93555e --- /dev/null +++ b/routers/api/devcontainer/vo/UpdateRepoDevcontainerVO.go @@ -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"` +} diff --git a/routers/api/devstar_ssh/key_pair/GenerateNewSSHSessionKeyPair.go b/routers/api/devstar_ssh/key_pair/GenerateNewSSHSessionKeyPair.go index afbafdca56..d3ba08007f 100644 --- a/routers/api/devstar_ssh/key_pair/GenerateNewSSHSessionKeyPair.go +++ b/routers/api/devstar_ssh/key_pair/GenerateNewSSHSessionKeyPair.go @@ -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) diff --git a/routers/install/install.go b/routers/install/install.go index ae29c7a7de..3a4a2d4ed5 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -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) diff --git a/routers/web/devcontainer/CreateRepoDevContainer.go b/routers/web/devcontainer/CreateRepoDevContainer.go deleted file mode 100644 index bce7779bea..0000000000 --- a/routers/web/devcontainer/CreateRepoDevContainer.go +++ /dev/null @@ -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 -} diff --git a/routers/web/devcontainer/DeleteRepoDevContainerForCurrentActor.go b/routers/web/devcontainer/DeleteRepoDevContainerForCurrentActor.go deleted file mode 100644 index 94d2b1f772..0000000000 --- a/routers/web/devcontainer/DeleteRepoDevContainerForCurrentActor.go +++ /dev/null @@ -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") -} diff --git a/routers/web/devcontainer/GetRepoDevContainerDetails.go b/routers/web/devcontainer/GetRepoDevContainerDetails.go deleted file mode 100644 index 67b49e7154..0000000000 --- a/routers/web/devcontainer/GetRepoDevContainerDetails.go +++ /dev/null @@ -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) -} diff --git a/routers/web/devcontainer/devcontainer.go b/routers/web/devcontainer/devcontainer.go new file mode 100644 index 0000000000..f8551349b2 --- /dev/null +++ b/routers/web/devcontainer/devcontainer.go @@ -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") +} diff --git a/routers/web/devcontainer/workflowTask.go b/routers/web/devcontainer/workflowTask.go deleted file mode 100644 index fba5fba039..0000000000 --- a/routers/web/devcontainer/workflowTask.go +++ /dev/null @@ -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"` -} diff --git a/routers/web/user/setting/dev_containers_list.go b/routers/web/user/setting/dev_containers_list.go index 86372b47e3..b5b71bcc09 100644 --- a/routers/web/user/setting/dev_containers_list.go +++ b/routers/web/user/setting/dev_containers_list.go @@ -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 ( diff --git a/routers/web/web.go b/routers/web/web.go index f0df1d7b60..d6c9cb9f6c 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -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. 已登录 diff --git a/services/devstar_devcontainer/OpenDevcontainerAbstractAgentService.go b/services/devcontainer/OpenDevcontainerAbstractAgentService.go similarity index 72% rename from services/devstar_devcontainer/OpenDevcontainerAbstractAgentService.go rename to services/devcontainer/OpenDevcontainerAbstractAgentService.go index 951c99460d..93e2f130be 100644 --- a/services/devstar_devcontainer/OpenDevcontainerAbstractAgentService.go +++ b/services/devcontainer/OpenDevcontainerAbstractAgentService.go @@ -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{ diff --git a/services/devstar_devcontainer/RepoDevcontainerAbstractAgentService.go b/services/devcontainer/RepoDevcontainerAbstractAgentService.go similarity index 72% rename from services/devstar_devcontainer/RepoDevcontainerAbstractAgentService.go rename to services/devcontainer/RepoDevcontainerAbstractAgentService.go index dcb596fe03..adf675fa02 100644 --- a/services/devstar_devcontainer/RepoDevcontainerAbstractAgentService.go +++ b/services/devcontainer/RepoDevcontainerAbstractAgentService.go @@ -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 } diff --git a/services/devstar_devcontainer/UserDevcontainerService.go b/services/devcontainer/UserDevcontainerService.go similarity index 92% rename from services/devstar_devcontainer/UserDevcontainerService.go rename to services/devcontainer/UserDevcontainerService.go index 0d3525fde3..804ec877a2 100644 --- a/services/devstar_devcontainer/UserDevcontainerService.go +++ b/services/devcontainer/UserDevcontainerService.go @@ -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(), } diff --git a/services/devstar_devcontainer/api_services/CreateDevcontainerAPIService.go b/services/devcontainer/api_services/CreateDevcontainerAPIService.go similarity index 92% rename from services/devstar_devcontainer/api_services/CreateDevcontainerAPIService.go rename to services/devcontainer/api_services/CreateDevcontainerAPIService.go index 4048df8484..bb0adf5aa9 100644 --- a/services/devstar_devcontainer/api_services/CreateDevcontainerAPIService.go +++ b/services/devcontainer/api_services/CreateDevcontainerAPIService.go @@ -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 diff --git a/services/devstar_devcontainer/api_services/DeleteDevcontainerAPIService.go b/services/devcontainer/api_services/DeleteDevcontainerAPIService.go similarity index 87% rename from services/devstar_devcontainer/api_services/DeleteDevcontainerAPIService.go rename to services/devcontainer/api_services/DeleteDevcontainerAPIService.go index 5f7f6a1c06..fbc8fbb651 100644 --- a/services/devstar_devcontainer/api_services/DeleteDevcontainerAPIService.go +++ b/services/devcontainer/api_services/DeleteDevcontainerAPIService.go @@ -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 diff --git a/services/devstar_devcontainer/api_services/OpenDevcontainerAPIService.go b/services/devcontainer/api_services/OpenDevcontainerAPIService.go similarity index 85% rename from services/devstar_devcontainer/api_services/OpenDevcontainerAPIService.go rename to services/devcontainer/api_services/OpenDevcontainerAPIService.go index c7cbc0236d..f71a047cc4 100644 --- a/services/devstar_devcontainer/api_services/OpenDevcontainerAPIService.go +++ b/services/devcontainer/api_services/OpenDevcontainerAPIService.go @@ -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) diff --git a/services/devcontainer/api_services/UpdateDevcontainerAPIService.go b/services/devcontainer/api_services/UpdateDevcontainerAPIService.go new file mode 100644 index 0000000000..5b367329b1 --- /dev/null +++ b/services/devcontainer/api_services/UpdateDevcontainerAPIService.go @@ -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: + //默认处理 + } +} diff --git a/services/devstar_devcontainer/dto/CreateDevcontainerDTO.go b/services/devcontainer/dto/CreateDevcontainerDTO.go similarity index 100% rename from services/devstar_devcontainer/dto/CreateDevcontainerDTO.go rename to services/devcontainer/dto/CreateDevcontainerDTO.go diff --git a/services/devstar_devcontainer/errors/ErrDevcontainerAlreadyCreated.go b/services/devcontainer/errors/ErrDevcontainerAlreadyCreated.go similarity index 100% rename from services/devstar_devcontainer/errors/ErrDevcontainerAlreadyCreated.go rename to services/devcontainer/errors/ErrDevcontainerAlreadyCreated.go diff --git a/services/devstar_devcontainer/errors/ErrDevcontainerNotFound.go b/services/devcontainer/errors/ErrDevcontainerNotFound.go similarity index 100% rename from services/devstar_devcontainer/errors/ErrDevcontainerNotFound.go rename to services/devcontainer/errors/ErrDevcontainerNotFound.go diff --git a/services/devstar_devcontainer/errors/ErrFailedOperateDevcontainer.go b/services/devcontainer/errors/ErrFailedOperateDevcontainer.go similarity index 100% rename from services/devstar_devcontainer/errors/ErrFailedOperateDevcontainer.go rename to services/devcontainer/errors/ErrFailedOperateDevcontainer.go diff --git a/services/devstar_devcontainer/errors/ErrIllegalParams.go b/services/devcontainer/errors/ErrIllegalParams.go similarity index 100% rename from services/devstar_devcontainer/errors/ErrIllegalParams.go rename to services/devcontainer/errors/ErrIllegalParams.go diff --git a/services/devstar_devcontainer/k8s_agent/AssignDevcontainerCreation2K8sOperator.go b/services/devcontainer/k8s_agent/AssignDevcontainerCreation2K8sOperator.go similarity index 96% rename from services/devstar_devcontainer/k8s_agent/AssignDevcontainerCreation2K8sOperator.go rename to services/devcontainer/k8s_agent/AssignDevcontainerCreation2K8sOperator.go index 27f9d3ab22..d1489f7011 100644 --- a/services/devstar_devcontainer/k8s_agent/AssignDevcontainerCreation2K8sOperator.go +++ b/services/devcontainer/k8s_agent/AssignDevcontainerCreation2K8sOperator.go @@ -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 主容器启动命令注意事项: diff --git a/services/devstar_devcontainer/k8s_agent/AssignDevcontainerDeletion2K8sOperator.go b/services/devcontainer/k8s_agent/AssignDevcontainerDeletion2K8sOperator.go similarity index 97% rename from services/devstar_devcontainer/k8s_agent/AssignDevcontainerDeletion2K8sOperator.go rename to services/devcontainer/k8s_agent/AssignDevcontainerDeletion2K8sOperator.go index 4ea4e5e108..e13d19ce54 100644 --- a/services/devstar_devcontainer/k8s_agent/AssignDevcontainerDeletion2K8sOperator.go +++ b/services/devcontainer/k8s_agent/AssignDevcontainerDeletion2K8sOperator.go @@ -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{ diff --git a/services/devstar_devcontainer/k8s_agent/AssignDevcontainerGetting2K8sOperator.go b/services/devcontainer/k8s_agent/AssignDevcontainerGetting2K8sOperator.go similarity index 89% rename from services/devstar_devcontainer/k8s_agent/AssignDevcontainerGetting2K8sOperator.go rename to services/devcontainer/k8s_agent/AssignDevcontainerGetting2K8sOperator.go index ca39f92196..e03f369910 100644 --- a/services/devstar_devcontainer/k8s_agent/AssignDevcontainerGetting2K8sOperator.go +++ b/services/devcontainer/k8s_agent/AssignDevcontainerGetting2K8sOperator.go @@ -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) diff --git a/services/devstar_devcontainer/k8s_agent/errors/ErrDeleteDevcontainers.go b/services/devcontainer/k8s_agent/errors/ErrDeleteDevcontainers.go similarity index 100% rename from services/devstar_devcontainer/k8s_agent/errors/ErrDeleteDevcontainers.go rename to services/devcontainer/k8s_agent/errors/ErrDeleteDevcontainers.go diff --git a/services/devstar_devcontainer/k8s_agent/errors/ErrIllegalK8sAgentParams.go b/services/devcontainer/k8s_agent/errors/ErrIllegalK8sAgentParams.go similarity index 100% rename from services/devstar_devcontainer/k8s_agent/errors/ErrIllegalK8sAgentParams.go rename to services/devcontainer/k8s_agent/errors/ErrIllegalK8sAgentParams.go diff --git a/services/devstar_devcontainer/options/AbstractDeleteDevcontainerOptions.go b/services/devcontainer/options/AbstractDeleteDevcontainerOptions.go similarity index 100% rename from services/devstar_devcontainer/options/AbstractDeleteDevcontainerOptions.go rename to services/devcontainer/options/AbstractDeleteDevcontainerOptions.go diff --git a/services/devcontainer/options/AbstractOpenDevcontainerOptions.go b/services/devcontainer/options/AbstractOpenDevcontainerOptions.go new file mode 100644 index 0000000000..198da52046 --- /dev/null +++ b/services/devcontainer/options/AbstractOpenDevcontainerOptions.go @@ -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 +} diff --git a/services/devstar_devcontainer/options/CreateDevcontainerOptions.go b/services/devcontainer/options/CreateDevcontainerOptions.go similarity index 100% rename from services/devstar_devcontainer/options/CreateDevcontainerOptions.go rename to services/devcontainer/options/CreateDevcontainerOptions.go diff --git a/services/devcontainer/options/OpenDevcontainerAppDispatcherOptions.go b/services/devcontainer/options/OpenDevcontainerAppDispatcherOptions.go new file mode 100644 index 0000000000..77ab724a56 --- /dev/null +++ b/services/devcontainer/options/OpenDevcontainerAppDispatcherOptions.go @@ -0,0 +1,8 @@ +package options + +type OpenDevcontainerAppDispatcherOptions struct { + Name string `json:"name"` + Wait bool `json:"wait"` + Port uint16 + UserPublicKey string +} diff --git a/services/devcontainer/options/UpdateDevcontainerOptions.go b/services/devcontainer/options/UpdateDevcontainerOptions.go new file mode 100644 index 0000000000..27e3e2f0ab --- /dev/null +++ b/services/devcontainer/options/UpdateDevcontainerOptions.go @@ -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 +} diff --git a/services/devstar_devcontainer/vo/OpenDevcontainerAbstractAgentVO.go b/services/devcontainer/vo/OpenDevcontainerAbstractAgentVO.go similarity index 100% rename from services/devstar_devcontainer/vo/OpenDevcontainerAbstractAgentVO.go rename to services/devcontainer/vo/OpenDevcontainerAbstractAgentVO.go diff --git a/services/devstar_cloud_provider/CreateNATRulePort.go b/services/devstar_cloud_provider/CreateNATRulePort.go index 61d06fdb0f..645d06b21d 100644 --- a/services/devstar_cloud_provider/CreateNATRulePort.go +++ b/services/devstar_cloud_provider/CreateNATRulePort.go @@ -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) diff --git a/services/devstar_cloud_provider/DeleteNATRulePort.go b/services/devstar_cloud_provider/DeleteNATRulePort.go index b07394a73a..b08a094a31 100644 --- a/services/devstar_cloud_provider/DeleteNATRulePort.go +++ b/services/devstar_cloud_provider/DeleteNATRulePort.go @@ -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) diff --git a/services/devstar_cloud_provider/tencent/nat_port_mapping/DevstarCloudTencentNATPortMappingService.go b/services/devstar_cloud_provider/tencent/nat_port_mapping/DevstarCloudTencentNATPortMappingService.go index ec66c63e26..547f402061 100644 --- a/services/devstar_cloud_provider/tencent/nat_port_mapping/DevstarCloudTencentNATPortMappingService.go +++ b/services/devstar_cloud_provider/tencent/nat_port_mapping/DevstarCloudTencentNATPortMappingService.go @@ -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{ diff --git a/services/devstar_cloud_provider/tencent/nat_port_mapping/GetTencentNATRuleClient.go b/services/devstar_cloud_provider/tencent/nat_port_mapping/GetTencentNATRuleClient.go index 5e535200d0..5ccbfca17d 100644 --- a/services/devstar_cloud_provider/tencent/nat_port_mapping/GetTencentNATRuleClient.go +++ b/services/devstar_cloud_provider/tencent/nat_port_mapping/GetTencentNATRuleClient.go @@ -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) } diff --git a/services/devstar_devcontainer/docker_agent/AssignDevcontainerGettingDockerOperator.go b/services/devstar_devcontainer/docker_agent/AssignDevcontainerGettingDockerOperator.go deleted file mode 100644 index cca7833a70..0000000000 --- a/services/devstar_devcontainer/docker_agent/AssignDevcontainerGettingDockerOperator.go +++ /dev/null @@ -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, - } -} diff --git a/services/devstar_devcontainer/options/AbstractOpenDevcontainerOptions.go b/services/devstar_devcontainer/options/AbstractOpenDevcontainerOptions.go deleted file mode 100644 index e21878f3d2..0000000000 --- a/services/devstar_devcontainer/options/AbstractOpenDevcontainerOptions.go +++ /dev/null @@ -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 层查询数据库 -} diff --git a/services/devstar_devcontainer/options/OpenDevcontainerAppDispatcherOptions.go b/services/devstar_devcontainer/options/OpenDevcontainerAppDispatcherOptions.go deleted file mode 100644 index 053678946c..0000000000 --- a/services/devstar_devcontainer/options/OpenDevcontainerAppDispatcherOptions.go +++ /dev/null @@ -1,7 +0,0 @@ -package options - -type OpenDevcontainerAppDispatcherOptions struct { - Name string `json:"name"` - Wait bool `json:"wait"` - Port uint16 -} diff --git a/services/devstar_ssh_key_pair/api_service/GenerateNewRSASSHSessionKeyPair.go b/services/devstar_ssh_key_pair/api_service/GenerateNewRSASSHSessionKeyPair.go index 9cbe252689..a847680287 100644 --- a/services/devstar_ssh_key_pair/api_service/GenerateNewRSASSHSessionKeyPair.go +++ b/services/devstar_ssh_key_pair/api_service/GenerateNewRSASSHSessionKeyPair.go @@ -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), } } diff --git a/services/repository/repository.go b/services/repository/repository.go index e66c9f228c..b888351639 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -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" diff --git a/templates/repo/devcontainer/details.tmpl b/templates/repo/devcontainer/details.tmpl index 6cc5579ef9..b6891fd5b5 100644 --- a/templates/repo/devcontainer/details.tmpl +++ b/templates/repo/devcontainer/details.tmpl @@ -61,6 +61,9 @@

*See GET /api/devcontainer with param 'repoId' and 'wait'

{{end}} + @@ -74,6 +77,10 @@
{{svg "octicon-person" 16 "tw-mr-2"}} {{.ContextUser.Name}}
{{if .HasDevContainer}} + + + + {{else if .HasValidDevContainerJSON}} {{template "base/modal_actions_confirm" .}} + + {{template "base/footer" .}}