Files
devstar/services/devcontainer/devcontainer_utils.go

236 lines
5.9 KiB
Go
Raw Permalink Normal View History

2025-08-16 18:31:14 +08:00
package devcontainer
import (
2025-09-04 10:48:46 +08:00
"archive/tar"
2025-08-16 18:31:14 +08:00
"context"
"fmt"
"io"
2025-09-18 17:57:29 +08:00
"net"
2025-08-16 18:31:14 +08:00
"net/url"
"regexp"
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
gitea_context "code.gitea.io/gitea/services/context"
"github.com/google/uuid"
)
func IsAdmin(ctx context.Context, doer *user.User, repoID int64) (bool, error) {
if doer.IsAdmin {
return true, nil
}
e := db.GetEngine(ctx)
teamMember, err := e.Table("team_user").
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode = ? ",
repoID, perm.AccessModeAdmin).
And("team_user.uid = ?", doer.ID).Exist()
if err != nil {
return false, nil
}
if teamMember {
return true, nil
}
return e.Get(&repo.Collaboration{RepoID: repoID, UserID: doer.ID, Mode: 3})
}
func GetFileContentByPath(ctx context.Context, repo *repo.Repository, path string) (string, error) {
2025-09-18 17:19:14 +08:00
var err error
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
if err != nil {
return "", err
}
2025-08-16 18:31:14 +08:00
// 1. 获取默认分支名
branchName := repo.DefaultBranch
if len(branchName) == 0 {
2025-09-18 17:19:14 +08:00
branchName = cfg.Section("repository").Key("DEFAULT_BRANCH").Value()
2025-08-16 18:31:14 +08:00
}
// 2. 打开默认分支
gitRepoEntity, err := git.OpenRepository(ctx, repo.RepoPath())
if err != nil {
return "", err
}
defer func(gitRepoEntity *git.Repository) {
_ = gitRepoEntity.Close()
}(gitRepoEntity)
// 3. 获取分支名称
commit, err := gitRepoEntity.GetBranchCommit(branchName)
if err != nil {
return "", err
}
entry, err := commit.GetTreeEntryByPath(path)
if err != nil {
return "", err
}
// No way to edit a directory online.
if entry.IsDir() {
2025-09-18 19:19:57 +08:00
return "", fmt.Errorf("%s entry.IsDir", path)
2025-08-16 18:31:14 +08:00
}
blob := entry.Blob()
if blob.Size() >= setting.UI.MaxDisplayFileSize {
2025-09-18 19:19:57 +08:00
return "", fmt.Errorf("%s blob.Size overflow", path)
2025-08-16 18:31:14 +08:00
}
dataRc, err := blob.DataAsync()
if err != nil {
return "", err
}
defer dataRc.Close()
buf := make([]byte, 1024)
n, _ := util.ReadAtMost(dataRc, buf)
buf = buf[:n]
// Only some file types are editable online as text.
if !typesniffer.DetectContentType(buf).IsRepresentableAsText() {
return "", fmt.Errorf("typesniffer.IsRepresentableAsText")
}
d, _ := io.ReadAll(dataRc)
buf = append(buf, d...)
if content, err := charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true}); err != nil {
log.Error("ToUTF8: %v", err)
return string(buf), nil
} else {
return content, nil
}
}
// FileExists returns true if a file exists in the given repo branch
func FileExists(path string, repo *gitea_context.Repository) (bool, error) {
var branch = repo.BranchName
if branch == "" {
branch = repo.Repository.DefaultBranch
}
commit, err := repo.GitRepo.GetBranchCommit(branch)
if err != nil {
return false, err
}
if _, err := commit.GetTreeEntryByPath(path); err != nil {
return false, err
}
return true, nil
}
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
}
func getSanitizedDevcontainerName(username, repoName string) string {
regexpNonAlphaNum := regexp.MustCompile(`[^a-zA-Z0-9]`)
sanitizedUsername := regexpNonAlphaNum.ReplaceAllString(username, "")
sanitizedRepoName := regexpNonAlphaNum.ReplaceAllString(repoName, "")
if len(sanitizedUsername) > 15 {
sanitizedUsername = strings.ToLower(sanitizedUsername[:15])
}
if len(sanitizedRepoName) > 31 {
sanitizedRepoName = strings.ToLower(sanitizedRepoName[:31])
}
newUUID, _ := uuid.NewUUID()
uuidStr := newUUID.String()
uuidStr = regexpNonAlphaNum.ReplaceAllString(uuidStr, "")[:15]
return fmt.Sprintf("%s-%s-%s", sanitizedUsername, sanitizedRepoName, uuidStr)
}
func ReplacePortOfUrl(originalURL, targetPort string) (string, error) {
// 解析原始 URL
parsedURL, _ := url.Parse(originalURL)
2025-09-18 17:57:29 +08:00
// 获取主机名和端口号
host, _, err := net.SplitHostPort(parsedURL.Host)
if err != nil {
// 如果没有端口号,则 SplitHostPort 会返回错误
// 这种情况下Host 就是主机名
host = parsedURL.Host
2025-08-16 18:31:14 +08:00
}
2025-09-18 17:57:29 +08:00
// 重新组装 Host 和端口
parsedURL.Host = net.JoinHostPort(host, targetPort)
2025-08-16 18:31:14 +08:00
// 生成新的 URL 字符串
newURL := parsedURL.String()
return newURL, nil
}
2025-09-18 18:37:42 +08:00
func GetPortFromURL(urlStr string) (string, error) {
parsedURL, err := url.Parse(urlStr)
if err != nil {
return "", fmt.Errorf("解析URL失败: %v", err)
}
// 获取主机名和端口号
_, port, err := net.SplitHostPort(parsedURL.Host)
if err != nil {
// 如果SplitHostPort失败说明URL中没有明确指定端口
// 需要根据协议判断默认端口
switch parsedURL.Scheme {
case "http":
return "80", nil
case "https":
return "443", nil
default:
return "", fmt.Errorf("未知协议: %s", parsedURL.Scheme)
}
}
// 如果端口存在,直接返回
return port, nil
}
2025-09-04 10:48:46 +08:00
// addFileToTar 将文件添加到 tar 归档
func AddFileToTar(tw *tar.Writer, filename string, content string, mode int64) error {
hdr := &tar.Header{
Name: filename,
Mode: mode,
Size: int64(len(content)),
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
if _, err := tw.Write([]byte(content)); err != nil {
return err
}
return nil
}