package devcontainer import ( "context" "fmt" "regexp" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/setting" gitea_web_context "code.gitea.io/gitea/services/context" devcontainer_service_errors "code.gitea.io/gitea/services/devcontainer/errors" ) // CreateDevcontainerAPIService API专用创建 DevContainer Service func CreateDevcontainerAPIService(ctx *gitea_web_context.Context, opts *CreateDevcontainerOptions) error { // 0. 检查用户传入参数 if ctx == nil || opts == nil || opts.Actor == nil || opts.RepoId <= 0 { return devcontainer_service_errors.ErrIllegalParams{ FieldNameList: []string{"ctx", "opts", "opts.Actor", "opts.RepoId"}, } } // sanitize user-input SSH Public Key List regexpInvalidSSHPublicKey := regexp.MustCompile(`[\r]`) for _, publicKey := range opts.SSHPublicKeyList { if len(publicKey) <= 4 || publicKey[0:4] != "ssh-" || regexpInvalidSSHPublicKey.MatchString(publicKey) { // 遇到可能无效的 SSH Public Key,或者导致 k8s Operator YAML 解码失败的字符: `\r`,报错返回 // ERROR Reconciler error: // { // "controller": "devcontainerapp", // "controllerGroup": "devcontainer.devstar.cn", // "controllerKind": "DevcontainerApp", // "DevcontainerApp": { // "name": "leviyanx-16-4834a4c88c4511ef9c1a4e1bce2a7080", // "namespace": "devstar-studio-ns" // }, // "namespace": "devstar-studio-ns", // "name": "leviyanx-16-4834a4c88c4511ef9c1a4e1bce2a7080", // "reconcileID": "6af51347-7aae-4542-a5cf-2f9b57a202e6", // "error": "panic: error converting YAML to JSON: yaml: line 46: could not find expected ':' [recovered]" // } return devcontainer_service_errors.ErrIllegalParams{ FieldNameList: []string{"SSHPublicKeyList"}, } } } // 1. 开启事务 errTxn := db.WithTx(ctx, func(ctx context.Context) error { // 1.1 调用 model层,查询数据库,将 repoId 变换为 Repository 对象 repositoryInDB, err := repo.GetRepositoryByID(ctx, opts.RepoId) if err != nil || repositoryInDB == nil { return devcontainer_service_errors.ErrIllegalParams{ FieldNameList: []string{"opts.RepoId"}, } } // 1.2 检查该用户在该仓库 是否已经创建过 DevContainer optsRepoDevcontainer := &RepoDevcontainerOptions{ Actor: opts.Actor, Repository: repositoryInDB, } devcontainerDetails, err := GetRepoDevcontainerDetails(ctx, optsRepoDevcontainer) if err != nil || devcontainerDetails.DevContainerId > 0 { return devcontainer_service_errors.ErrDevcontainerAlreadyCreated{ Actor: opts.Actor, Repository: repositoryInDB, } } // 1.3 调用 DevContainer Service 创建 DevContainer optsCreateDevcontainer := &CreateRepoDevcontainerOptions{ Actor: opts.Actor, Repository: repositoryInDB, SSHPublicKeyList: opts.SSHPublicKeyList, } return CreateRepoDevcontainer(ctx, optsCreateDevcontainer) }) return errTxn } // OpenDevcontainerAPIService API 专用获取 DevContainer Service func OpenDevcontainerAPIService(ctx *gitea_web_context.Context, opts *AbstractOpenDevcontainerOptions) (*RepoDevContainer, error) { // 0. 检查用户传入参数 if ctx == nil || opts == nil || opts.Actor == nil || opts.RepoId <= 0 { return nil, devcontainer_service_errors.ErrIllegalParams{ FieldNameList: []string{"ctx", "opts", "opts.Actor", "opts.RepoId"}, } } var devcontainerDetails RepoDevContainer // 1. 开启数据库事务,查询 某用户在某仓库的 DevContainer errTxn := db.WithTx(ctx, func(ctx context.Context) error { // 1.1 调用 model层,查询数据库,将 repoId 变换为 Repository 对象 repositoryInDB, err := repo.GetRepositoryByID(ctx, opts.RepoId) if err != nil || repositoryInDB == nil { return devcontainer_service_errors.ErrIllegalParams{ FieldNameList: []string{"opts.RepoId"}, } } // 1.2 检查该用户在该仓库 是否已经创建过 DevContainer optsRepoDevcontainer := &RepoDevcontainerOptions{ Actor: opts.Actor, Repository: repositoryInDB, } devcontainerDetails, err = GetRepoDevcontainerDetails(ctx, optsRepoDevcontainer) if err != nil || devcontainerDetails.DevContainerId <= 0 { return devcontainer_service_errors.ErrDevcontainerNotFound{ Actor: opts.Actor, Repository: repositoryInDB, } } // 1.3 查询 DevContainer 成功,结束数据库事务 return nil }) if errTxn != nil { return nil, errTxn } // 2. 调用抽象层获取 DevContainer 最新状态(需要根据用户传入的 wait 参数决定是否要阻塞等待 DevContainer 就绪) optsOpenDevcontainer := &OpenDevcontainerAppDispatcherOptions{ RepoID: opts.RepoId, UserID: opts.Actor.ID, Name: devcontainerDetails.DevContainerName, Port: devcontainerDetails.DevContainerPort, Wait: opts.Wait, UserPublicKey: opts.UserPublicKey, } openDevcontainerAbstractAgentVO, err := OpenDevcontainerService(ctx, optsOpenDevcontainer) if err != nil { return nil, err } // 3. 更新VO,合并成为真正的 SSH连接信息 devcontainerDetails.DevContainerPort = openDevcontainerAbstractAgentVO.NodePortAssigned return &devcontainerDetails, nil } func UpdateDevcontainerAPIService(ctx *gitea_web_context.Context, opts *UpdateDevcontainerOptions) error { switch setting.Devcontainer.Agent { case setting.KUBERNETES: //k8s处理 return fmt.Errorf("暂时不支持的Agent") case setting.DOCKER: return SaveDevcontainer(ctx, opts) default: return fmt.Errorf("不支持的Agent") //默认处理 } } // DeleteDevcontainerAPIService API 专用删除 DevContainer Service func DeleteDevcontainerAPIService(ctx *gitea_web_context.Context, opts *AbstractDeleteDevcontainerOptions) error { // 0. 检查用户传入参数 if ctx == nil || opts == nil || opts.Actor == nil || opts.RepoId <= 0 { return devcontainer_service_errors.ErrIllegalParams{ FieldNameList: []string{"ctx", "opts", "opts.Actor", "opts.RepoId"}, } } // 1. 开启数据库事务,查询 某用户在某仓库的 DevContainer errTxn := db.WithTx(ctx, func(ctx context.Context) error { // 1.1 调用 model层,查询数据库,将 repoId 变换为 Repository 对象 repositoryInDB, err := repo.GetRepositoryByID(ctx, opts.RepoId) if err != nil || repositoryInDB == nil { return devcontainer_service_errors.ErrIllegalParams{ FieldNameList: []string{"opts.RepoId"}, } } // 1.2 直接尝试删除 DevContainer,若报错,则说明不存在 DevContainer optsRepoDevcontainer := &RepoDevcontainerOptions{ Actor: opts.Actor, Repository: repositoryInDB, } err = DeleteRepoDevcontainer(ctx, optsRepoDevcontainer) if err != nil { return devcontainer_service_errors.ErrDevcontainerNotFound{ Actor: opts.Actor, Repository: repositoryInDB, } } // 1.3 查询 DevContainer 成功,结束数据库事务 return nil }) // 2. 返回删除结果 return errTxn }