Some checks failed
DevStar Studio Auto Test Pipeline / unit-frontend-test (pull_request) Failing after 1m34s
DevStar Studio Auto Test Pipeline / unit-backend-test (pull_request) Failing after 33s
DevStar Studio CI/CD Pipeline / build-and-push-x86-64-docker-image (pull_request) Successful in 7m21s
DevStar E2E Test / e2e-test (pull_request) Successful in 8m47s
391 lines
10 KiB
Go
391 lines
10 KiB
Go
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||
// SPDX-License-Identifier: MIT
|
||
|
||
package devcontainer
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"net/http"
|
||
"strings"
|
||
|
||
"code.gitea.io/gitea/models/db"
|
||
devcontainer_model "code.gitea.io/gitea/models/devcontainer"
|
||
user_model "code.gitea.io/gitea/models/user"
|
||
"code.gitea.io/gitea/modules/log"
|
||
"code.gitea.io/gitea/modules/setting"
|
||
"code.gitea.io/gitea/modules/templates"
|
||
"code.gitea.io/gitea/modules/util"
|
||
"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 (
|
||
tplRepoSecrets templates.TplName = "repo/settings/devcontainer"
|
||
tplOrgSecrets templates.TplName = "org/settings/devcontainer"
|
||
tplUserSecrets templates.TplName = "user/settings/devcontainer"
|
||
)
|
||
|
||
type secretsCtx struct {
|
||
OwnerID int64
|
||
RepoID int64
|
||
IsRepo bool
|
||
IsOrg bool
|
||
IsUser bool
|
||
SecretsTemplate templates.TplName
|
||
RedirectLink string
|
||
}
|
||
|
||
func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) {
|
||
if ctx.Data["PageIsRepoSettings"] == true {
|
||
return &secretsCtx{
|
||
OwnerID: 0,
|
||
RepoID: ctx.Repo.Repository.ID,
|
||
IsRepo: true,
|
||
SecretsTemplate: tplRepoSecrets,
|
||
RedirectLink: ctx.Repo.RepoLink + "/settings/devcontainer/secrets",
|
||
}, nil
|
||
}
|
||
|
||
if ctx.Data["PageIsOrgSettings"] == true {
|
||
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
|
||
ctx.ServerError("RenderUserOrgHeader", err)
|
||
return nil, nil
|
||
}
|
||
return &secretsCtx{
|
||
OwnerID: ctx.ContextUser.ID,
|
||
RepoID: 0,
|
||
IsOrg: true,
|
||
SecretsTemplate: tplOrgSecrets,
|
||
RedirectLink: ctx.Org.OrgLink + "/settings/devcontainer/secrets",
|
||
}, nil
|
||
}
|
||
|
||
if ctx.Data["PageIsUserSettings"] == true {
|
||
return &secretsCtx{
|
||
OwnerID: ctx.Doer.ID,
|
||
RepoID: 0,
|
||
IsUser: true,
|
||
SecretsTemplate: tplUserSecrets,
|
||
RedirectLink: setting.AppSubURL + "/user/settings/devcontainer/secrets",
|
||
}, nil
|
||
}
|
||
|
||
return nil, errors.New("unable to set Secrets context")
|
||
}
|
||
|
||
func Secrets(ctx *context.Context) {
|
||
ctx.Data["Title"] = ctx.Tr("devcontainer.secrets")
|
||
ctx.Data["PageType"] = "secrets"
|
||
ctx.Data["PageIsSharedSettingsDevcontainerSecrets"] = true
|
||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||
|
||
sCtx, err := getSecretsCtx(ctx)
|
||
if err != nil {
|
||
ctx.ServerError("getSecretsCtx", err)
|
||
return
|
||
}
|
||
|
||
if sCtx.IsRepo {
|
||
ctx.Data["DisableSSH"] = setting.SSH.Disabled
|
||
}
|
||
|
||
// 查询本地密钥
|
||
secrets, err := db.Find[devcontainer_model.DevcontainerSecret](ctx, devcontainer_model.FindSecretsOpts{
|
||
OwnerID: sCtx.OwnerID,
|
||
RepoID: sCtx.RepoID,
|
||
})
|
||
if err != nil {
|
||
ctx.ServerError("FindSecrets", err)
|
||
return
|
||
}
|
||
|
||
// 查询已启用的密钥(tags)
|
||
var tags []string
|
||
err = db.GetEngine(ctx).
|
||
Select("variable_name").
|
||
Table("devcontainer_script").
|
||
Where("user_id = ? AND repo_id = ?", sCtx.OwnerID, sCtx.RepoID).
|
||
Find(&tags)
|
||
if err != nil {
|
||
log.Error("Get script names for secrets: %v", err)
|
||
}
|
||
|
||
// 将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 = "[]"
|
||
}
|
||
|
||
// 设置模板数据
|
||
ctx.Data["Secrets"] = secrets
|
||
ctx.Data["Tags"] = tagsJSONStr
|
||
ctx.Data["DataMaxLength"] = devcontainer_model.SecretDataMaxLength
|
||
ctx.Data["DescriptionMaxLength"] = devcontainer_model.SecretDescriptionMaxLength
|
||
|
||
ctx.HTML(http.StatusOK, sCtx.SecretsTemplate)
|
||
}
|
||
|
||
func SecretCreate(ctx *context.Context) {
|
||
sCtx, err := getSecretsCtx(ctx)
|
||
if err != nil {
|
||
ctx.ServerError("getSecretsCtx", err)
|
||
return
|
||
}
|
||
|
||
if ctx.HasError() {
|
||
ctx.JSONError(ctx.GetErrMsg())
|
||
return
|
||
}
|
||
|
||
form := web.GetForm(ctx).(*forms.AddSecretForm)
|
||
|
||
s, err := devcontainer_service.CreateSecret(ctx, sCtx.OwnerID, sCtx.RepoID, form.Name, util.ReserveLineBreakForTextarea(form.Data), form.Description)
|
||
if err != nil {
|
||
log.Error("CreateSecret: %v", err)
|
||
ctx.JSONError(ctx.Tr("devcontainer.secrets.creation.failed"))
|
||
return
|
||
}
|
||
|
||
ctx.Flash.Success(ctx.Tr("devcontainer.secrets.creation.success", s.Name))
|
||
ctx.JSONRedirect(sCtx.RedirectLink)
|
||
}
|
||
|
||
func SecretUpdate(ctx *context.Context) {
|
||
sCtx, err := getSecretsCtx(ctx)
|
||
if err != nil {
|
||
ctx.ServerError("getSecretsCtx", err)
|
||
return
|
||
}
|
||
|
||
if ctx.HasError() {
|
||
ctx.JSONError(ctx.GetErrMsg())
|
||
return
|
||
}
|
||
|
||
id := ctx.PathParamInt64("secret_id")
|
||
|
||
secret := findSecret(ctx, id, sCtx)
|
||
if ctx.Written() {
|
||
return
|
||
}
|
||
|
||
form := web.GetForm(ctx).(*forms.AddSecretForm)
|
||
|
||
err = devcontainer_model.UpdateSecret(ctx, secret.ID, util.ReserveLineBreakForTextarea(form.Data), form.Description)
|
||
if err != nil {
|
||
log.Error("UpdateSecret: %v", err)
|
||
ctx.JSONError(ctx.Tr("devcontainer.secrets.update.failed"))
|
||
return
|
||
}
|
||
|
||
ctx.Flash.Success(ctx.Tr("devcontainer.secrets.update.success"))
|
||
ctx.JSONRedirect(sCtx.RedirectLink)
|
||
}
|
||
|
||
func findSecret(ctx *context.Context, id int64, sCtx *secretsCtx) *devcontainer_model.DevcontainerSecret {
|
||
opts := devcontainer_model.FindSecretsOpts{
|
||
SecretID: id,
|
||
}
|
||
switch {
|
||
case sCtx.IsRepo:
|
||
opts.RepoID = sCtx.RepoID
|
||
if opts.RepoID == 0 {
|
||
panic("RepoID is 0")
|
||
}
|
||
case sCtx.IsOrg, sCtx.IsUser:
|
||
opts.OwnerID = sCtx.OwnerID
|
||
if opts.OwnerID == 0 {
|
||
panic("OwnerID is 0")
|
||
}
|
||
default:
|
||
panic("invalid secret context")
|
||
}
|
||
got, err := devcontainer_model.FindSecrets(ctx, opts)
|
||
if err != nil {
|
||
ctx.ServerError("FindSecrets", err)
|
||
return nil
|
||
} else if len(got) == 0 {
|
||
ctx.NotFound(nil)
|
||
return nil
|
||
}
|
||
return got[0]
|
||
}
|
||
|
||
func SecretDelete(ctx *context.Context) {
|
||
sCtx, err := getSecretsCtx(ctx)
|
||
if err != nil {
|
||
ctx.ServerError("getSecretsCtx", err)
|
||
return
|
||
}
|
||
|
||
id := ctx.PathParamInt64("secret_id")
|
||
|
||
secret := findSecret(ctx, id, sCtx)
|
||
if ctx.Written() {
|
||
return
|
||
}
|
||
|
||
if err := devcontainer_service.DeleteSecretByID(ctx, sCtx.OwnerID, sCtx.RepoID, secret.ID); err != nil {
|
||
log.Error("Delete secret [%d] failed: %v", id, err)
|
||
ctx.JSONError(ctx.Tr("devcontainer.secrets.deletion.failed"))
|
||
return
|
||
}
|
||
|
||
// Delete corresponding script record
|
||
script := &devcontainer_model.DevcontainerScript{
|
||
UserId: sCtx.OwnerID,
|
||
RepoId: sCtx.RepoID,
|
||
VariableName: secret.Name,
|
||
}
|
||
_, err = db.GetEngine(ctx).Delete(script)
|
||
if err != nil {
|
||
log.Error("Delete script for secret [%d] failed: %v", id, err)
|
||
// Note: We log the error but don't interrupt the secret deletion process
|
||
}
|
||
|
||
ctx.Flash.Success(ctx.Tr("devcontainer.secrets.deletion.success"))
|
||
ctx.JSONRedirect(sCtx.RedirectLink)
|
||
}
|
||
|
||
func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) {
|
||
secrets, err := devcontainer_model.FindSecrets(ctx, devcontainer_model.FindSecretsOpts{OwnerID: ownerID, RepoID: repoID})
|
||
if err != nil {
|
||
ctx.ServerError("FindSecrets", err)
|
||
return
|
||
}
|
||
|
||
ctx.Data["Secrets"] = secrets
|
||
ctx.Data["DataMaxLength"] = devcontainer_model.SecretDataMaxLength
|
||
ctx.Data["DescriptionMaxLength"] = devcontainer_model.SecretDescriptionMaxLength
|
||
|
||
// Get script names for secrets
|
||
var scriptNames []string
|
||
err = db.GetEngine(ctx).
|
||
Select("variable_name").
|
||
Table("devcontainer_script").
|
||
Where("(user_id = 0 AND repo_id = 0) OR (user_id = ? AND repo_id = 0) OR (user_id = 0 AND repo_id = ?)", ownerID, repoID).
|
||
Find(&scriptNames)
|
||
if err != nil {
|
||
log.Error("Get script names for secrets: %v", err)
|
||
}
|
||
|
||
// Filter secrets that have scripts
|
||
var secretNamesWithScripts []string
|
||
for _, secret := range secrets {
|
||
if contains(scriptNames, secret.Name) {
|
||
secretNamesWithScripts = append(secretNamesWithScripts, secret.Name)
|
||
}
|
||
}
|
||
|
||
ctx.Data["Tags"] = secretNamesWithScripts
|
||
|
||
// Set Link for template
|
||
if ctx.Data["PageIsRepoSettings"] == true {
|
||
ctx.Data["Link"] = ctx.Repo.RepoLink + "/settings/devcontainer/secrets"
|
||
} else if ctx.Data["PageIsOrgSettings"] == true {
|
||
ctx.Data["Link"] = ctx.Org.OrgLink + "/settings/devcontainer/secrets"
|
||
} else if ctx.Data["PageIsUserSettings"] == true {
|
||
ctx.Data["Link"] = setting.AppSubURL + "/user/settings/devcontainer/secrets"
|
||
}
|
||
}
|
||
|
||
func contains(slice []string, item string) bool {
|
||
for _, s := range slice {
|
||
if s == item {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func SecretScriptCreate(ctx *context.Context) {
|
||
sCtx, err := getSecretsCtx(ctx)
|
||
if err != nil {
|
||
ctx.ServerError("getSecretsCtx", err)
|
||
return
|
||
}
|
||
|
||
if ctx.HasError() {
|
||
ctx.JSONError(ctx.GetErrMsg())
|
||
return
|
||
}
|
||
|
||
query := ctx.Req.URL.Query()
|
||
secretName := strings.ToUpper(query.Get("name"))
|
||
|
||
// Check if secret exists
|
||
secrets, err := devcontainer_model.FindSecrets(ctx, devcontainer_model.FindSecretsOpts{
|
||
OwnerID: sCtx.OwnerID,
|
||
RepoID: sCtx.RepoID,
|
||
Name: secretName,
|
||
})
|
||
if err != nil {
|
||
log.Error("Check secret existence: %v", err)
|
||
ctx.JSONError(ctx.Tr("devcontainer.secrets.creation.failed"))
|
||
return
|
||
}
|
||
if len(secrets) == 0 {
|
||
log.Error("Secret %s does not exist", secretName)
|
||
ctx.JSONError(ctx.Tr("devcontainer.secrets.creation.failed"))
|
||
return
|
||
}
|
||
|
||
// Create devcontainer_script record
|
||
script := &devcontainer_model.DevcontainerScript{
|
||
UserId: sCtx.OwnerID,
|
||
RepoId: sCtx.RepoID,
|
||
VariableName: secretName,
|
||
}
|
||
|
||
_, err = db.GetEngine(ctx).Insert(script)
|
||
if err != nil {
|
||
log.Error("CreateSecretScript: %v", err)
|
||
ctx.JSONError(ctx.Tr("devcontainer.secrets.creation.failed"))
|
||
return
|
||
}
|
||
|
||
ctx.JSONOK()
|
||
}
|
||
|
||
func SecretScriptDelete(ctx *context.Context) {
|
||
sCtx, err := getSecretsCtx(ctx)
|
||
if err != nil {
|
||
ctx.ServerError("getSecretsCtx", err)
|
||
return
|
||
}
|
||
|
||
if ctx.HasError() {
|
||
ctx.JSONError(ctx.GetErrMsg())
|
||
return
|
||
}
|
||
|
||
query := ctx.Req.URL.Query()
|
||
secretName := strings.ToUpper(query.Get("name"))
|
||
|
||
// Delete devcontainer_script record
|
||
script := &devcontainer_model.DevcontainerScript{
|
||
UserId: sCtx.OwnerID,
|
||
RepoId: sCtx.RepoID,
|
||
VariableName: secretName,
|
||
}
|
||
|
||
_, err = db.GetEngine(ctx).Delete(script)
|
||
if err != nil {
|
||
log.Error("DeleteSecretScript: %v", err)
|
||
ctx.JSONError(ctx.Tr("devcontainer.secrets.deletion.failed"))
|
||
return
|
||
}
|
||
|
||
ctx.JSONOK()
|
||
}
|