Files
devstar/routers/web/devcontainer/secrets.go
hwy a1bc0c9187
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
devcontainer添加密匙
2025-12-16 13:49:22 +08:00

391 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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()
}