426 lines
12 KiB
Go
426 lines
12 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||
// SPDX-License-Identifier: MIT
|
||
|
||
package devcontainer
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"io"
|
||
"net/http"
|
||
"strings"
|
||
|
||
"code.gitea.io/gitea/models/db"
|
||
devcontainer_model "code.gitea.io/gitea/models/devcontainer"
|
||
"code.gitea.io/gitea/modules/log"
|
||
"code.gitea.io/gitea/modules/setting"
|
||
"code.gitea.io/gitea/modules/templates"
|
||
"code.gitea.io/gitea/modules/web"
|
||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||
"code.gitea.io/gitea/services/context"
|
||
devcontainer_service "code.gitea.io/gitea/services/devcontainer"
|
||
"code.gitea.io/gitea/services/forms"
|
||
)
|
||
|
||
const (
|
||
tplRepoVariables templates.TplName = "repo/settings/devcontainer"
|
||
tplOrgVariables templates.TplName = "org/settings/devcontainer"
|
||
tplUserVariables templates.TplName = "user/settings/devcontainer"
|
||
tplAdminVariables templates.TplName = "admin/devcontainer"
|
||
)
|
||
|
||
type variablesCtx struct {
|
||
OwnerID int64
|
||
RepoID int64
|
||
IsRepo bool
|
||
IsOrg bool
|
||
IsUser bool
|
||
IsGlobal bool
|
||
VariablesTemplate templates.TplName
|
||
RedirectLink string
|
||
}
|
||
|
||
func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
|
||
if ctx.Data["PageIsRepoSettings"] == true {
|
||
return &variablesCtx{
|
||
OwnerID: 0,
|
||
RepoID: ctx.Repo.Repository.ID,
|
||
IsRepo: true,
|
||
VariablesTemplate: tplRepoVariables,
|
||
RedirectLink: ctx.Repo.RepoLink + "/settings/devcontainer/variables",
|
||
}, nil
|
||
}
|
||
|
||
if ctx.Data["PageIsOrgSettings"] == true {
|
||
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
|
||
ctx.ServerError("RenderUserOrgHeader", err)
|
||
return nil, nil
|
||
}
|
||
return &variablesCtx{
|
||
OwnerID: ctx.ContextUser.ID,
|
||
RepoID: 0,
|
||
IsOrg: true,
|
||
VariablesTemplate: tplOrgVariables,
|
||
RedirectLink: ctx.Org.OrgLink + "/settings/devcontainer/variables",
|
||
}, nil
|
||
}
|
||
|
||
if ctx.Data["PageIsUserSettings"] == true {
|
||
return &variablesCtx{
|
||
OwnerID: ctx.Doer.ID,
|
||
RepoID: 0,
|
||
IsUser: true,
|
||
VariablesTemplate: tplUserVariables,
|
||
RedirectLink: setting.AppSubURL + "/user/settings/devcontainer/variables",
|
||
}, nil
|
||
}
|
||
|
||
if ctx.Data["PageIsAdmin"] == true {
|
||
return &variablesCtx{
|
||
OwnerID: 0,
|
||
RepoID: 0,
|
||
IsGlobal: true,
|
||
VariablesTemplate: tplAdminVariables,
|
||
RedirectLink: setting.AppSubURL + "/-/admin/devcontainer/variables",
|
||
}, nil
|
||
}
|
||
|
||
return nil, errors.New("unable to set Variables context")
|
||
}
|
||
|
||
func Variables(ctx *context.Context) {
|
||
ctx.Data["Title"] = ctx.Tr("devcontainer.variables")
|
||
ctx.Data["PageType"] = "variables"
|
||
ctx.Data["PageIsSharedSettingsDevcontainerVariables"] = true
|
||
|
||
vCtx, err := getVariablesCtx(ctx)
|
||
if err != nil {
|
||
ctx.ServerError("getVariablesCtx", err)
|
||
return
|
||
}
|
||
|
||
variables, err := db.Find[devcontainer_model.DevcontainerVariable](ctx, devcontainer_model.FindVariablesOpts{
|
||
OwnerID: vCtx.OwnerID,
|
||
RepoID: vCtx.RepoID,
|
||
})
|
||
if err != nil {
|
||
ctx.ServerError("FindVariables", err)
|
||
return
|
||
}
|
||
|
||
var tags []string
|
||
|
||
// 使用JOIN查询,关联DevcontainerScript表和devcontainer_variable表
|
||
err = db.GetEngine(ctx).
|
||
Select("variable_name").
|
||
Table("devcontainer_script").
|
||
Where("user_id = ? AND repo_id = ?", vCtx.OwnerID, vCtx.RepoID).
|
||
Find(&tags)
|
||
|
||
// 将tags转换为JSON格式的字符串
|
||
tagsJSON, err := json.Marshal(tags)
|
||
if err != nil {
|
||
ctx.ServerError("Marshal tags", err)
|
||
return
|
||
}
|
||
// 确保tagsJSON不为null
|
||
tagsJSONStr := string(tagsJSON)
|
||
if tagsJSONStr == "null" {
|
||
tagsJSONStr = "[]"
|
||
}
|
||
// 创建一个新的请求
|
||
req, err := http.NewRequest("GET", "http://devstar.cn/variables/export", nil)
|
||
if err != nil {
|
||
ctx.Data["DevstarVariables"] = []*devcontainer_model.DevcontainerVariable{}
|
||
} else {
|
||
client := &http.Client{}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
ctx.Data["DevstarVariables"] = []*devcontainer_model.DevcontainerVariable{}
|
||
} else {
|
||
defer resp.Body.Close()
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
ctx.Data["DevstarVariables"] = []*devcontainer_model.DevcontainerVariable{}
|
||
} else {
|
||
var devstarVariables []*devcontainer_model.DevcontainerVariable
|
||
err = json.Unmarshal(body, &devstarVariables)
|
||
if err != nil {
|
||
ctx.Data["DevstarVariables"] = []*devcontainer_model.DevcontainerVariable{}
|
||
} else {
|
||
// 创建一个本地变量名称的映射,用于快速查找
|
||
localVariableNames := make(map[string]bool)
|
||
for _, variable := range variables {
|
||
localVariableNames[variable.Name] = true
|
||
}
|
||
|
||
// 筛选出不与本地变量同名的devstar变量
|
||
var filteredDevstarVariables []*devcontainer_model.DevcontainerVariable
|
||
for _, devstarVar := range devstarVariables {
|
||
if !localVariableNames[devstarVar.Name] {
|
||
filteredDevstarVariables = append(filteredDevstarVariables, devstarVar)
|
||
}
|
||
}
|
||
|
||
ctx.Data["DevstarVariables"] = filteredDevstarVariables
|
||
}
|
||
}
|
||
}
|
||
}
|
||
ctx.Data["Variables"] = variables
|
||
ctx.Data["Tags"] = tagsJSONStr
|
||
ctx.Data["DescriptionMaxLength"] = devcontainer_model.VariableDescriptionMaxLength
|
||
ctx.HTML(http.StatusOK, vCtx.VariablesTemplate)
|
||
}
|
||
func GetExportVariables(ctx *context.Context) {
|
||
globalVariables, err := db.Find[devcontainer_model.DevcontainerVariable](ctx, devcontainer_model.FindVariablesOpts{})
|
||
if err != nil {
|
||
ctx.ServerError("Get Global Variables", err)
|
||
return
|
||
}
|
||
// 筛选出键以"DEVSTAR_"开头的脚本
|
||
var devstarVariables []devcontainer_model.DevcontainerVariable
|
||
for _, value := range globalVariables {
|
||
if strings.HasPrefix(value.Name, "DEVSTAR_") {
|
||
devstarVariables = append(devstarVariables, *value)
|
||
}
|
||
}
|
||
ctx.JSON(http.StatusOK, devstarVariables)
|
||
}
|
||
func VariableCreate(ctx *context.Context) {
|
||
vCtx, err := getVariablesCtx(ctx)
|
||
if err != nil {
|
||
ctx.ServerError("getVariablesCtx", err)
|
||
return
|
||
}
|
||
|
||
if ctx.HasError() { // form binding validation error
|
||
ctx.JSONError(ctx.GetErrMsg())
|
||
return
|
||
}
|
||
|
||
form := web.GetForm(ctx).(*forms.EditVariableForm)
|
||
|
||
v, err := devcontainer_service.CreateVariable(ctx, vCtx.OwnerID, vCtx.RepoID, form.Name, form.Data, form.Description)
|
||
if err != nil {
|
||
log.Error("CreateVariable: %v", err)
|
||
ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
|
||
return
|
||
}
|
||
|
||
ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name))
|
||
ctx.JSONRedirect(vCtx.RedirectLink)
|
||
}
|
||
func ScriptCreate(ctx *context.Context) {
|
||
vCtx, err := getVariablesCtx(ctx)
|
||
if err != nil {
|
||
ctx.ServerError("getVariablesCtx", err)
|
||
return
|
||
}
|
||
|
||
if ctx.HasError() { // form binding validation error
|
||
ctx.JSONError(ctx.GetErrMsg())
|
||
return
|
||
}
|
||
query := ctx.Req.URL.Query()
|
||
var script *devcontainer_model.DevcontainerScript
|
||
// 首先检查变量是否在DevcontainerVariable表中存在
|
||
exists, err := db.GetEngine(ctx).
|
||
Table("devcontainer_variable").
|
||
Where("(owner_id = 0 AND repo_id = 0) OR (owner_id = ? AND repo_id = 0) OR (owner_id = 0 AND repo_id = ?)", vCtx.OwnerID, vCtx.RepoID).
|
||
And("name = ?", strings.ToUpper(query.Get("name"))).
|
||
Exist()
|
||
if err != nil {
|
||
log.Error("Check variable existence: %v", err)
|
||
ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
|
||
return
|
||
}
|
||
if !exists {
|
||
// 创建一个新的请求来获取devstar变量
|
||
req, err := http.NewRequest("GET", "http://devstar.cn/variables/export", nil)
|
||
if err != nil {
|
||
log.Error("Failed to create request for devstar variables: %v", err)
|
||
ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
|
||
return
|
||
}
|
||
|
||
client := &http.Client{}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
log.Error("Failed to fetch devstar variables: %v", err)
|
||
ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
|
||
return
|
||
}
|
||
|
||
defer resp.Body.Close()
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
log.Error("Failed to read devstar variables response: %v", err)
|
||
ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
|
||
return
|
||
}
|
||
|
||
var devstarVariables []*devcontainer_model.DevcontainerVariable
|
||
err = json.Unmarshal(body, &devstarVariables)
|
||
if err != nil {
|
||
log.Error("Failed to unmarshal devstar variables: %v", err)
|
||
ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
|
||
return
|
||
}
|
||
|
||
// 查找是否有匹配的devstar变量
|
||
foundInDevstar := false
|
||
searchName := strings.ToUpper(query.Get("name"))
|
||
for _, devstarVar := range devstarVariables {
|
||
if devstarVar.Name == searchName {
|
||
foundInDevstar = true
|
||
break
|
||
}
|
||
}
|
||
|
||
if !foundInDevstar {
|
||
log.Error("Variable %s does not exist", query.Get("name"))
|
||
ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
|
||
return
|
||
}
|
||
|
||
}
|
||
// 创建devcontainer_script记录
|
||
script = &devcontainer_model.DevcontainerScript{
|
||
UserId: vCtx.OwnerID,
|
||
RepoId: vCtx.RepoID,
|
||
VariableName: strings.ToUpper(query.Get("name")),
|
||
}
|
||
|
||
_, err = db.GetEngine(ctx).Insert(script)
|
||
if err != nil {
|
||
log.Error("CreateScript: %v", err)
|
||
ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
|
||
return
|
||
}
|
||
|
||
}
|
||
func VariableUpdate(ctx *context.Context) {
|
||
vCtx, err := getVariablesCtx(ctx)
|
||
if err != nil {
|
||
ctx.ServerError("getVariablesCtx", err)
|
||
return
|
||
}
|
||
|
||
if ctx.HasError() { // form binding validation error
|
||
ctx.JSONError(ctx.GetErrMsg())
|
||
return
|
||
}
|
||
|
||
id := ctx.PathParamInt64("variable_id")
|
||
|
||
variable := findActionsVariable(ctx, id, vCtx)
|
||
if ctx.Written() {
|
||
return
|
||
}
|
||
|
||
form := web.GetForm(ctx).(*forms.EditVariableForm)
|
||
variable.Name = form.Name
|
||
variable.Data = form.Data
|
||
variable.Description = form.Description
|
||
|
||
if ok, err := devcontainer_service.UpdateVariableNameData(ctx, variable); err != nil || !ok {
|
||
log.Error("UpdateVariable: %v", err)
|
||
ctx.JSONError(ctx.Tr("actions.variables.update.failed"))
|
||
return
|
||
}
|
||
ctx.Flash.Success(ctx.Tr("actions.variables.update.success"))
|
||
ctx.JSONRedirect(vCtx.RedirectLink)
|
||
}
|
||
|
||
func findActionsVariable(ctx *context.Context, id int64, vCtx *variablesCtx) *devcontainer_model.DevcontainerVariable {
|
||
opts := devcontainer_model.FindVariablesOpts{
|
||
IDs: []int64{id},
|
||
}
|
||
switch {
|
||
case vCtx.IsRepo:
|
||
opts.RepoID = vCtx.RepoID
|
||
if opts.RepoID == 0 {
|
||
panic("RepoID is 0")
|
||
}
|
||
case vCtx.IsOrg, vCtx.IsUser:
|
||
opts.OwnerID = vCtx.OwnerID
|
||
if opts.OwnerID == 0 {
|
||
panic("OwnerID is 0")
|
||
}
|
||
case vCtx.IsGlobal:
|
||
// do nothing
|
||
default:
|
||
panic("invalid actions variable")
|
||
}
|
||
got, err := devcontainer_model.FindVariables(ctx, opts)
|
||
if err != nil {
|
||
ctx.ServerError("FindVariables", err)
|
||
return nil
|
||
} else if len(got) == 0 {
|
||
ctx.NotFound(nil)
|
||
return nil
|
||
}
|
||
return got[0]
|
||
}
|
||
|
||
func VariableDelete(ctx *context.Context) {
|
||
vCtx, err := getVariablesCtx(ctx)
|
||
if err != nil {
|
||
ctx.ServerError("getVariablesCtx", err)
|
||
return
|
||
}
|
||
|
||
id := ctx.PathParamInt64("variable_id")
|
||
|
||
variable := findActionsVariable(ctx, id, vCtx)
|
||
if ctx.Written() {
|
||
return
|
||
}
|
||
|
||
if err := devcontainer_service.DeleteVariableByID(ctx, variable.ID); err != nil {
|
||
log.Error("Delete variable [%d] failed: %v", id, err)
|
||
ctx.JSONError(ctx.Tr("actions.variables.deletion.failed"))
|
||
return
|
||
}
|
||
// 删除相应的script记录,根据repoId、userId和name
|
||
script := &devcontainer_model.DevcontainerScript{
|
||
UserId: vCtx.OwnerID,
|
||
RepoId: vCtx.RepoID,
|
||
VariableName: variable.Name,
|
||
}
|
||
_, err = db.GetEngine(ctx).Delete(script)
|
||
if err != nil {
|
||
log.Error("Delete script for variable [%d] failed: %v", id, err)
|
||
// 注意:这里我们记录错误但不中断变量删除过程
|
||
}
|
||
ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success"))
|
||
ctx.JSONRedirect(vCtx.RedirectLink)
|
||
}
|
||
func ScriptDelete(ctx *context.Context) {
|
||
vCtx, err := getVariablesCtx(ctx)
|
||
if err != nil {
|
||
ctx.ServerError("getVariablesCtx", err)
|
||
return
|
||
}
|
||
|
||
if ctx.HasError() { // form binding validation error
|
||
ctx.JSONError(ctx.GetErrMsg())
|
||
return
|
||
}
|
||
query := ctx.Req.URL.Query()
|
||
// 删除devcontainer_script记录
|
||
script := &devcontainer_model.DevcontainerScript{
|
||
UserId: vCtx.OwnerID,
|
||
RepoId: vCtx.RepoID,
|
||
VariableName: query.Get("name"),
|
||
}
|
||
_, err = db.GetEngine(ctx).Delete(script)
|
||
if err != nil {
|
||
log.Error("DeleteScript: %v", err)
|
||
ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
|
||
return
|
||
}
|
||
|
||
}
|