diff --git a/internal/app/app.go b/internal/app/app.go index 4bb8cac..6f9878a 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -18,7 +18,7 @@ func New() (*App, error) { return nil, err } - db.AutoMigrate(&models.Task{}, &models.GitRepoAltRepoTask{}) + db.AutoMigrate(&models.Task{}, &models.GitRepoAltRepoTask{}, &models.RPMFile{}) db.FirstOrCreate(&models.ALTRepo{ Name: "Sisyphus", diff --git a/internal/controllers/taskcontroller/create.go b/internal/controllers/taskcontroller/create.go index 8184a86..7c5f126 100644 --- a/internal/controllers/taskcontroller/create.go +++ b/internal/controllers/taskcontroller/create.go @@ -14,7 +14,7 @@ type CreateTaskDTO struct { } type CreateTaskResponse struct { - TaskID int `json:"taskID"` + TaskID uint `json:"taskID"` Status models.TaskStatus `json:"status"` } diff --git a/internal/models/db.go b/internal/models/db.go index 9e05c8f..46a995a 100644 --- a/internal/models/db.go +++ b/internal/models/db.go @@ -7,19 +7,23 @@ import ( type ALTRepo struct { gorm.Model - ID int Name string `gorm:"uniqueIndex"` } type GitRepo struct { - ID int + gorm.Model + Name string `gorm:"uniqueIndex"` } type RPMFile struct { + gorm.Model + TaskID int - Name string - Arch string + Task Task + + Name string + Arch string } type TaskStatus int @@ -45,27 +49,29 @@ const ( type Task struct { gorm.Model - ID int Status TaskStatus Type TaskType - RepoID int - Repo GitRepo - ALTRepoID int + RepoID uint + Repo *GitRepo + ALTRepoID uint ALTRepo ALTRepo - Files []RPMFile + FilesRemoved bool + Files []RPMFile } type GitRepoAltRepoTask struct { gorm.Model - ID int + RepoID uint `gorm:"uniqueIndex:idx_gr_ar_gitaltrepotask"` + Repo *GitRepo + ALTRepoID uint `gorm:"uniqueIndex:idx_gr_ar_gitaltrepotask"` + ALTRepo *ALTRepo - RepoID int `gorm:"uniqueIndex:idx_gr_ar_gitaltrepotask"` - Repo GitRepo - ALTRepoID int `gorm:"uniqueIndex:idx_gr_ar_gitaltrepotask"` - ALTRepo ALTRepo - TaskID int - Task Task + LastTaskID uint + LastTask *Task + + CurrentTaskID uint + CurrentTask Task } diff --git a/internal/router/router.go b/internal/router/router.go index 85d1bfb..21cdfb6 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -7,6 +7,7 @@ import ( "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/app" "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/controllers/taskcontroller" "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/middlewares" + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/services/reposervice" "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/services/taskservice" ) @@ -28,6 +29,9 @@ func (r *Router) Setup() *chi.Mux { r.app, ) + repoService := reposervice.New(r.app) + repoService.ForceUpdate() + taskController := taskcontroller.New( r.app, taskService, diff --git a/internal/services/reposervice/service.go b/internal/services/reposervice/service.go new file mode 100644 index 0000000..bac9519 --- /dev/null +++ b/internal/services/reposervice/service.go @@ -0,0 +1,134 @@ +package reposervice + +import ( + "fmt" + "os" + "os/exec" + "path" + "strconv" + + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/app" + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/models" + "gorm.io/gorm" +) + +type Service struct { + app *app.App +} + +func New(app *app.App) *Service { + return &Service{ + app: app, + } +} + +func createSymlink(target, link string) error { + if _, err := os.Lstat(link); err == nil { + if err := os.Remove(link); err != nil { + return fmt.Errorf("failed to remove existing file or symlink: %w", err) + } + } + + if err := os.Symlink(target, link); err != nil { + return fmt.Errorf("failed to create symlink: %w", err) + } + + return nil +} + +func runGenbasedir(repoDir, arch, repoName string) { + cmd := exec.Command("genbasedir", "--bloat", "--progress", fmt.Sprintf("--topdir=%s", repoDir), arch, repoName) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + fmt.Printf("Failed to run genbasedir for %s: %v\n", arch, err) + os.Exit(1) + } + fmt.Printf("Successfully ran genbasedir for %s\n", arch) +} + +func createRepoDirs(repoDir, repoName, arch string) { + // Create the 'base' directory + baseDir := path.Join(repoDir, arch, "base") + err := os.MkdirAll(baseDir, os.ModePerm) + if err != nil { + fmt.Printf("Failed to create directory %s: %v\n", baseDir, err) + os.Exit(1) + } + + // Create the 'RPMS.' directory + rpmsDir := path.Join(repoDir, arch, fmt.Sprintf("RPMS.%s", repoName)) + err = os.MkdirAll(rpmsDir, os.ModePerm) + if err != nil { + fmt.Printf("Failed to create directory %s: %v\n", rpmsDir, err) + os.Exit(1) + } +} + +func (s *Service) ForceUpdate() { + var tasks []models.Task + altRepo := models.ALTRepo{ + Name: "Sisyphus", + } + + s.app.Db. + Where(&altRepo). + First(&altRepo) + + s.app.Db.Debug(). + Model(&models.GitRepoAltRepoTask{}). + Select("tasks.*"). + Joins("JOIN tasks ON tasks.id = git_repo_alt_repo_tasks.last_task_id"). + Where(&models.GitRepoAltRepoTask{ + ALTRepoID: altRepo.ID, + }). + Preload("Files"). + Find(&tasks) + + repoPath := path.Join(s.app.Config.UploadDir, "future_repo", "Sisyphus") + + os.MkdirAll( + repoPath, + os.ModePerm, + ) + + repoName := "aides" + architectures := []string{"x86_64", "noarch"} + + for _, arch := range architectures { + createRepoDirs(repoPath, repoName, arch) + } + + for _, el := range tasks { + fmt.Println(el.Files) + + taskPath := path.Join( + s.app.Config.UploadDir, "tasks", strconv.FormatUint(uint64(el.ID), 10), + ) + + for _, fileInfo := range el.Files { + localFilePath := path.Join( + strconv.FormatUint(uint64(el.ID), 10), fileInfo.Name, + ) + symLink := path.Join(s.app.Config.UploadDir, "future_repo", "Sisyphus", fileInfo.Arch, "RPMS.aides", fileInfo.Name) + targetPath := path.Join("../../../../tasks/", localFilePath) + createSymlink(targetPath, symLink) + + fmt.Println(path.Join(taskPath, fileInfo.Name)) + } + } + + for _, arch := range architectures { + runGenbasedir(repoPath, arch, repoName) + } + + s.app.Db.Debug(). + Model(&models.GitRepoAltRepoTask{}). + Where(&models.GitRepoAltRepoTask{ + ALTRepoID: altRepo.ID, + }). + Update( + "current_task_id", gorm.Expr("last_task_id"), + ) +} diff --git a/internal/services/taskservice/service.go b/internal/services/taskservice/service.go index 6b0eccc..c0e78f3 100644 --- a/internal/services/taskservice/service.go +++ b/internal/services/taskservice/service.go @@ -1,24 +1,99 @@ package taskservice import ( - "mime/multipart" + "os" + "path" + "strconv" "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/app" + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/models" ) type Service struct { app *app.App } -type TaskUploadInput struct { - TaskID string - Repo string - - Files []*multipart.FileHeader -} - func New(app *app.App) *Service { return &Service{ app: app, } } + +func (s *Service) onTaskComplete(task *models.Task) error { + if err := s.app.Db.Save(&task).Error; err != nil { + return err + } + + grart := models.GitRepoAltRepoTask{ + ALTRepoID: 1, + Repo: task.Repo, + } + s.app.Db.Debug().Where( + &grart, + ).FirstOrCreate(&grart) + + grart.LastTaskID = task.ID + + if err := s.app.Db.Save(&grart).Error; err != nil { + return err + } + + s.tasksCleanup(&grart, 0) + + return nil +} + +func (s *Service) tasksCleanup(r *models.GitRepoAltRepoTask, N int) { + excludedTaskIDs := []uint{} + if r.CurrentTaskID != 0 { + excludedTaskIDs = append(excludedTaskIDs, r.CurrentTaskID) + } + if r.LastTaskID != 0 { + excludedTaskIDs = append(excludedTaskIDs, r.LastTaskID) + } + + var lastNTaskIDs []uint + s.app.Db. + Debug(). + Table("tasks"). + Select("id"). + Where("repo_id = ?", r.ID). + Where("status = ?", models.StatusCompleted). + Where("id NOT IN ?", excludedTaskIDs). + Order("created_at DESC"). + Limit(N). + Pluck("id", &lastNTaskIDs) + + excludedTaskIDs = append(excludedTaskIDs, lastNTaskIDs...) + + var taskIDsToDelete []int + s.app.Db. + Debug(). + Model(&models.Task{}). + Select("id"). + Where("repo_id = ?", r.RepoID). + Where("files_removed = ?", false). + Where("id NOT IN ?", excludedTaskIDs). + Pluck("id", &taskIDsToDelete) + + if len(taskIDsToDelete) > 0 { + s.app.Db. + Debug(). + Model(&models.Task{}). + Where("id IN ?", taskIDsToDelete). + Update("files_removed", true) + + for _, id := range taskIDsToDelete { + s.removeTaskFiles(id) + } + } +} + +func (s *Service) removeTaskFiles(taskId int) { + taskFolderPath := path.Join( + s.app.Config.UploadDir, + "tasks", + strconv.Itoa(taskId), + ) + os.RemoveAll(taskFolderPath) +} diff --git a/internal/services/taskservice/upload.go b/internal/services/taskservice/upload.go index 4bd8973..0465c32 100644 --- a/internal/services/taskservice/upload.go +++ b/internal/services/taskservice/upload.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "mime/multipart" "os" "os/exec" "path" @@ -14,6 +15,13 @@ import ( "gorm.io/gorm" ) +type TaskUploadInput struct { + TaskID string + Repo string + + Files []*multipart.FileHeader +} + func getRPMArchitecture(filePath string) (string, error) { cmd := exec.Command("rpm", "-qp", "--queryformat", "%{ARCH}", filePath) @@ -34,11 +42,12 @@ func (s *Service) Upload(input *TaskUploadInput) error { files := input.Files task := models.Task{} - result := s.app.Db.Where( + result := s.app.Db.Preload("Repo").Where( "id = ?", taskID, - ).Where( - "status = ?", models.StatusPending, ).First(&task) + /*.Where( + "status = ?", models.StatusPending, + )*/ if errors.Is(result.Error, gorm.ErrRecordNotFound) { return result.Error @@ -47,8 +56,6 @@ func (s *Service) Upload(input *TaskUploadInput) error { return result.Error } - fmt.Printf("%v", task.Status) - localPath := path.Join(input.TaskID) taskFolderPath := path.Join(s.app.Config.UploadDir, "tasks", localPath) os.MkdirAll(taskFolderPath, os.ModePerm) @@ -91,7 +98,15 @@ func (s *Service) Upload(input *TaskUploadInput) error { return err } - fmt.Println(arch) + f := models.RPMFile{ + Name: fileHeader.Filename, + Arch: arch, + Task: task, + } + + if err := s.app.Db.Save(&f).Error; err != nil { + return err + } // Символическая ссылка /* @@ -106,9 +121,7 @@ func (s *Service) Upload(input *TaskUploadInput) error { } task.Status = models.StatusCompleted - if err := s.app.Db.Save(&task).Error; err != nil { - return err - } + s.onTaskComplete(&task) return nil }