Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions DockerFile
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ RUN go get github.com/akrylysov/algnhsa && \
FROM openeuler/openeuler:24.03
LABEL maintainer="Zhou Yi 1123678689@qq.com"

# 安装依赖工具
RUN dnf install -y git wget tar gzip && \
# 下载git-lfs
RUN dnf install -y git wget tar gzip tini && \
wget https://github.com/git-lfs/git-lfs/releases/download/v3.3.0/git-lfs-linux-amd64-v3.3.0.tar.gz && \
tar -xzf git-lfs-linux-amd64-v3.3.0.tar.gz && \
cd git-lfs-3.3.0 && \
Expand All @@ -26,4 +24,4 @@ COPY --chown=BigFiles:group --from=BUILDER /home/main /home/BigFiles/main
COPY --chown=BigFiles:group --from=BUILDER /home/scripts/lfsNameQuery.py /home/BigFiles/lfsNameQuery.py

EXPOSE 5000
ENTRYPOINT ["/home/BigFiles/main"]
ENTRYPOINT ["tini", "--", "/home/BigFiles/main"]
15 changes: 7 additions & 8 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,12 @@ func Init(cfg config.DBConfig) error {
},
)
if err != nil {
log.Fatal("Failed to connect to database", err)
return err
return fmt.Errorf("failed to connect to database: %w", err)
}

sqlDb, err := dbInstance.DB()
if err != nil {
return err
return fmt.Errorf("failed to get underlying sql.DB: %w", err)
}

sqlDb.SetConnMaxLifetime(cfg.GetLifeDuration())
Expand All @@ -46,6 +45,11 @@ func Init(cfg config.DBConfig) error {
return nil
}

// RunMigration performs schema auto-migration once at startup.
func RunMigration() error {
return Db.AutoMigrate(&LfsObj{})
}

// DB returns the current database instance.
func DB() *gorm.DB {
return Db
Expand All @@ -67,11 +71,6 @@ type LfsObj struct {

// InsertLFSObj 插入 LFS 元数据
func InsertLFSObj(obj LfsObj) error {
err := Db.AutoMigrate(&LfsObj{})
if err != nil {
return err
}

var existingObj LfsObj
if err := Db.Where("oid = ? AND repo = ? AND owner = ?", obj.Oid,
obj.Repo, obj.Owner).First(&existingObj).Error; err == nil {
Expand Down
80 changes: 57 additions & 23 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"github.com/huaweicloud/huaweicloud-sdk-go-obs/obs"
Expand Down Expand Up @@ -60,33 +62,32 @@ func gatherOptions(fs *flag.FlagSet, args ...string) (options, error) {
return o, err
}

func initConfig(cfg *config.Config) {
func initConfig(cfg *config.Config) error {
if err := server.Init(cfg); err != nil {
logrus.Errorf("load ValidateConfig, err:%s", err.Error())
return
return fmt.Errorf("load ValidateConfig: %w", err)
}

if err := auth.Init(cfg); err != nil {
logrus.Errorf("load gitee config, err:%s", err.Error())
return
return fmt.Errorf("load gitee config: %w", err)
}

if err := db.Init(cfg.DBConfig); err != nil {
logrus.Errorf("init database config, err:%s", err.Error())
return
return fmt.Errorf("init database config: %w", err)
}

return nil
}

func initObsClient(cfg *config.Config) {
func initObsClient(cfg *config.Config) error {
var err error
server.ObsClient, err = obs.New(cfg.ObsAccessKeyId, cfg.ObsSecretAccessKey,
cfg.ObsRegion, obs.WithSignature(obs.SignatureObs))
server.Bucket = cfg.LfsBucket
server.Prefit = cfg.Prefix
if err != nil {
logrus.Errorf("failed to initialize OBS client: %v", err.Error())
return
return fmt.Errorf("failed to initialize OBS client: %w", err)
}
return nil
}

func main() {
Expand All @@ -95,31 +96,54 @@ func main() {
os.Args[1:]...,
)
if err != nil {
logrus.Errorf("new options failed, err:%s", err.Error())
return
logrus.Fatalf("new options failed, err:%s", err.Error())
}

if err := o.Validate(); err != nil {
logrus.Errorf("Invalid options, err:%s", err.Error())
return
logrus.Fatalf("Invalid options, err:%s", err.Error())
}

if o.enableDebug {
logrus.SetLevel(logrus.DebugLevel)
logrus.Debug("debug enable.")
}

// Reap zombie child processes (e.g. git commands invoked by GetLFSMapping).
// Without this, zombie [git] processes accumulate and exhaust the node PID
// table, causing PIDPressure evictions.
sigChld := make(chan os.Signal, 1)
signal.Notify(sigChld, syscall.SIGCHLD)
go func() {
for range sigChld {
for {
// WNOHANG = 1: non-blocking wait — returns immediately if no child has exited.
pid, _ := syscall.Wait4(-1, nil, syscall.WNOHANG, nil)
if pid <= 0 {
break
}
}
}
}()

//cfg
cfg := new(config.Config)

if err := config.LoadConfig(o.service.ConfigFile, cfg, o.service.RemoveCfg); err != nil {
logrus.Errorf("load config, err:%s", err.Error())
return
logrus.Fatalf("load config, err:%s", err.Error())
}

if err := initObsClient(cfg); err != nil {
logrus.Fatalf("init OBS client failed: %v", err)
}

initObsClient(cfg)
if err := initConfig(cfg); err != nil {
logrus.Fatalf("init config failed: %v", err)
}

initConfig(cfg)
// Run database schema migration once at startup instead of on every insert.
if err := db.RunMigration(); err != nil {
logrus.Fatalf("run database migration failed: %v", err)
}

s, err := server.New(server.Options{
Prefix: cfg.Prefix,
Expand All @@ -132,6 +156,9 @@ func main() {
IsGithubAuthorized: auth.GithubAuth(),
SecretAccessKey: cfg.ObsSecretAccessKey,
})
if err != nil {
logrus.Fatalf("create server failed: %v", err)
}

go server.StartScheduledTask()
go server.ScheduledCheckOidAndFileName()
Expand All @@ -144,12 +171,19 @@ func main() {
IdleTimeout: 30 * time.Second,
}

if err != nil {
log.Fatalln(err)
}
// Graceful shutdown: listen for SIGTERM/SIGINT and call srv.Shutdown()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-quit
log.Println("shutting down server...")
if err := srv.Shutdown(nil); err != nil {
logrus.Errorf("server shutdown error: %v", err)
}
}()

log.Println("serving on http://0.0.0.0:5000 ...")
if err := srv.ListenAndServe(); err != nil {
log.Fatalln(err)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logrus.Fatalf("server error: %v", err)
}
}
94 changes: 73 additions & 21 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,14 @@ func (s *server) downloadObject(in *batch.RequestObject, out *batch.Object) {
getObjectInput.Expires = int(s.ttl / time.Second)
getObjectInput.Headers = map[string]string{contentType: obsHeader}
// 生成下载对象的带授权信息的URL
v := s.generateDownloadUrl(getObjectInput)
v, err := s.generateDownloadUrl(getObjectInput)
if err != nil {
out.Error = &batch.ObjectError{
Code: 500,
Message: err.Error(),
}
return
}

out.Actions = &batch.Actions{
Download: &batch.Action{
Expand Down Expand Up @@ -366,7 +373,11 @@ func (s *server) uploadObject(in *batch.RequestObject, out *batch.Object) {
putObjectInput.Headers = map[string]string{contentType: obsHeader}
putObjectOutput, err := s.client.CreateSignedUrl(putObjectInput)
if err != nil {
panic(err)
out.Error = &batch.ObjectError{
Code: 500,
Message: fmt.Sprintf("failed to create signed upload URL: %v", err),
}
return
}

out.Actions = &batch.Actions{
Expand All @@ -387,39 +398,64 @@ func (s *server) getObjectMetadataInput(key string) (output *obs.GetObjectMetada
}

// 生成下载对象的带授权信息的URL
func (s *server) generateDownloadUrl(getObjectInput *obs.CreateSignedUrlInput) *url.URL {
// 生成下载对象的带授权信息的URL
func (s *server) generateDownloadUrl(getObjectInput *obs.CreateSignedUrlInput) (*url.URL, error) {
getObjectOutput, err := s.client.CreateSignedUrl(getObjectInput)
if err != nil {
panic(err)
return nil, fmt.Errorf("failed to create signed download URL: %w", err)
}
v, err := url.Parse(getObjectOutput.SignedUrl)
if err == nil {
v.Host = s.cdnDomain
v.Scheme = "https"
} else {
logrus.Infof("%s cannot be parsed", getObjectOutput.SignedUrl)
panic(err)
if err != nil {
return nil, fmt.Errorf("failed to parse signed URL: %w", err)
}
return v
v.Host = s.cdnDomain
v.Scheme = "https"
return v, nil
}

func (s *server) healthCheck(w http.ResponseWriter, r *http.Request) {
response := batch.SuccessResponse{
Message: "Success",
Data: "healthCheck success",
w.Header().Set(contentType, jsonHeader)

dbOK := true
if db.Db != nil {
sqlDB, err := db.Db.DB()
if err != nil || sqlDB.Ping() != nil {
dbOK = false
}
} else {
dbOK = false
}

obsOK := true
if ObsClient != nil {
_, err := ObsClient.ListBuckets(nil)
if err != nil {
obsOK = false
}
} else {
obsOK = false
}

if !dbOK || !obsOK {
w.WriteHeader(http.StatusServiceUnavailable)
must(json.NewEncoder(w).Encode(batch.SuccessResponse{
Message: "Unhealthy",
Data: fmt.Sprintf("db=%v obs=%v", dbOK, obsOK),
}))
return
}

w.Header().Set(contentType, jsonHeader)
w.WriteHeader(http.StatusOK)
must(json.NewEncoder(w).Encode(response))
must(json.NewEncoder(w).Encode(batch.SuccessResponse{
Message: "Success",
Data: "healthCheck success",
}))
}

// --

func must(err error) {
if err != nil {
panic(err)
logrus.Errorf("encode response failed: %v", err)
}
}

Expand Down Expand Up @@ -454,14 +490,20 @@ func (s *server) download(w http.ResponseWriter, r *http.Request) {
Headers: map[string]string{contentType: obsHeader},
}

v := s.generateDownloadUrl(getObjectInput)
v, err := s.generateDownloadUrl(getObjectInput)
if err != nil {
w.Header().Set(contentType, jsonHeader)
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}

w.Header().Set(contentType, jsonHeader)

response := map[string]string{"url": v.String()}

w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(response)
err = json.NewEncoder(w).Encode(response)
if err != nil {
return
}
Expand Down Expand Up @@ -623,7 +665,17 @@ func checkOidFileName() {

}

const maxRepoOidNameDepth = 10

func checkRepoOidName(userInRepo auth.UserInRepo) (oidFileNameMap map[string]auth.FileInfo) {
return checkRepoOidNameWithDepth(userInRepo, 0)
}

func checkRepoOidNameWithDepth(userInRepo auth.UserInRepo, depth int) (oidFileNameMap map[string]auth.FileInfo) {
if depth >= maxRepoOidNameDepth {
logrus.Errorf("checkRepoOidName exceeded max depth %d for owner:%v repo:%v", maxRepoOidNameDepth, userInRepo.Owner, userInRepo.Repo)
return nil
}
oidFileNameMap, err := auth.GetLFSMapping(userInRepo)
if err != nil {
logrus.Errorf("get lfs mapping failed: %v", err)
Expand All @@ -638,7 +690,7 @@ func checkRepoOidName(userInRepo auth.UserInRepo) (oidFileNameMap map[string]aut
if repo.Parent.Fullname != "" {
userInRepo.Owner = strings.Split(repo.Parent.Fullname, "/")[0]
userInRepo.Repo = strings.Split(repo.Parent.Fullname, "/")[1]
return checkRepoOidName(userInRepo)
return checkRepoOidNameWithDepth(userInRepo, depth+1)
}
}
return oidFileNameMap
Expand Down
Loading
Loading