184 lines
5.2 KiB
Go
184 lines
5.2 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"path/filepath"
|
|
|
|
aptclone "github.com/syonad/clonepack/internal/clone/apt"
|
|
rpmclone "github.com/syonad/clonepack/internal/clone/rpm"
|
|
"github.com/syonad/clonepack/internal/store"
|
|
)
|
|
|
|
var validTypes = map[string]bool{"apt": true, "rpm": true, "docker": true, "binary": true}
|
|
|
|
type RepoService struct {
|
|
store store.RepoStore
|
|
dataDir string
|
|
}
|
|
|
|
func NewRepoService(s store.RepoStore, dataDir string) *RepoService {
|
|
return &RepoService{store: s, dataDir: dataDir}
|
|
}
|
|
|
|
var validSyncModes = map[string]bool{"auto": true, "manual": true}
|
|
|
|
type CreateRepoInput struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
SourceURL string `json:"source_url"`
|
|
SyncMode string `json:"sync_mode"`
|
|
AptSuite string `json:"apt_suite,omitempty"`
|
|
AptComponents []string `json:"apt_components,omitempty"`
|
|
AptArchitectures []string `json:"apt_architectures,omitempty"`
|
|
}
|
|
|
|
func (s *RepoService) Create(ctx context.Context, in CreateRepoInput) (*store.Repo, error) {
|
|
if in.Name == "" {
|
|
return nil, fmt.Errorf("name is required")
|
|
}
|
|
if !validTypes[in.Type] {
|
|
return nil, fmt.Errorf("invalid type: %s (must be apt, rpm, docker, or binary)", in.Type)
|
|
}
|
|
if in.SourceURL == "" {
|
|
return nil, fmt.Errorf("source_url is required")
|
|
}
|
|
if in.SyncMode == "" {
|
|
in.SyncMode = "auto"
|
|
} else if !validSyncModes[in.SyncMode] {
|
|
return nil, fmt.Errorf("invalid sync_mode: %s (must be auto or manual)", in.SyncMode)
|
|
}
|
|
|
|
var configJSON string
|
|
if in.Type == "apt" {
|
|
if in.AptSuite == "" {
|
|
return nil, fmt.Errorf("apt_suite is required for apt repositories")
|
|
}
|
|
if len(in.AptComponents) == 0 {
|
|
in.AptComponents = []string{"main"}
|
|
}
|
|
if len(in.AptArchitectures) == 0 {
|
|
in.AptArchitectures = []string{"amd64"}
|
|
}
|
|
cfg := aptclone.Config{
|
|
Suite: in.AptSuite,
|
|
Components: in.AptComponents,
|
|
Architectures: in.AptArchitectures,
|
|
}
|
|
b, err := json.Marshal(cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal apt config: %w", err)
|
|
}
|
|
configJSON = string(b)
|
|
}
|
|
|
|
r := &store.Repo{Name: in.Name, Type: in.Type, SourceURL: in.SourceURL, SyncMode: in.SyncMode, Config: configJSON}
|
|
id, err := s.store.CreateRepo(ctx, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
repo, err := s.store.GetRepo(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.initStorage(repo)
|
|
return repo, nil
|
|
}
|
|
|
|
func (s *RepoService) initStorage(repo *store.Repo) {
|
|
localDir := filepath.Join(s.dataDir, "repos", fmt.Sprintf("%d", repo.ID), repo.Type)
|
|
switch repo.Type {
|
|
case "rpm":
|
|
if err := rpmclone.InitEmptyRepo(localDir); err != nil {
|
|
slog.Warn("init storage failed", "repo_id", repo.ID, "error", err)
|
|
}
|
|
case "apt":
|
|
var cfg aptclone.Config
|
|
if err := json.Unmarshal([]byte(repo.Config), &cfg); err != nil {
|
|
slog.Warn("init storage: parse config failed", "repo_id", repo.ID, "error", err)
|
|
return
|
|
}
|
|
if err := aptclone.InitEmptyRepo(localDir, cfg); err != nil {
|
|
slog.Warn("init storage failed", "repo_id", repo.ID, "error", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
type UpdateRepoInput struct {
|
|
SourceURL *string
|
|
SyncMode *string
|
|
AptSuite *string
|
|
AptComponents []string
|
|
AptArchitectures []string
|
|
}
|
|
|
|
func (s *RepoService) Update(ctx context.Context, id int64, in UpdateRepoInput) (*store.Repo, error) {
|
|
repo, err := s.store.GetRepo(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if in.SourceURL != nil {
|
|
if *in.SourceURL == "" {
|
|
return nil, fmt.Errorf("source_url cannot be empty")
|
|
}
|
|
repo.SourceURL = *in.SourceURL
|
|
}
|
|
if in.SyncMode != nil {
|
|
if !validSyncModes[*in.SyncMode] {
|
|
return nil, fmt.Errorf("invalid sync_mode: %s (must be auto or manual)", *in.SyncMode)
|
|
}
|
|
repo.SyncMode = *in.SyncMode
|
|
}
|
|
|
|
if repo.Type == "apt" && (in.AptSuite != nil || in.AptComponents != nil || in.AptArchitectures != nil) {
|
|
var cfg aptclone.Config
|
|
if repo.Config != "" {
|
|
if err := json.Unmarshal([]byte(repo.Config), &cfg); err != nil {
|
|
return nil, fmt.Errorf("parse existing apt config: %w", err)
|
|
}
|
|
}
|
|
if in.AptSuite != nil {
|
|
if *in.AptSuite == "" {
|
|
return nil, fmt.Errorf("apt_suite cannot be empty")
|
|
}
|
|
cfg.Suite = *in.AptSuite
|
|
}
|
|
if in.AptComponents != nil {
|
|
cfg.Components = in.AptComponents
|
|
}
|
|
if in.AptArchitectures != nil {
|
|
cfg.Architectures = in.AptArchitectures
|
|
}
|
|
b, err := json.Marshal(cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal apt config: %w", err)
|
|
}
|
|
repo.Config = string(b)
|
|
} else if repo.Type != "apt" && (in.AptSuite != nil || in.AptComponents != nil || in.AptArchitectures != nil) {
|
|
return nil, fmt.Errorf("apt fields only apply to apt repositories")
|
|
}
|
|
|
|
if err := s.store.UpdateRepo(ctx, id, repo.SourceURL, repo.SyncMode, repo.Config); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.store.GetRepo(ctx, id)
|
|
}
|
|
|
|
func (s *RepoService) List(ctx context.Context) ([]store.Repo, error) {
|
|
return s.store.ListRepos(ctx)
|
|
}
|
|
|
|
func (s *RepoService) Get(ctx context.Context, id int64) (*store.Repo, error) {
|
|
return s.store.GetRepo(ctx, id)
|
|
}
|
|
|
|
func (s *RepoService) GetByName(ctx context.Context, name string) (*store.Repo, error) {
|
|
return s.store.GetRepoByName(ctx, name)
|
|
}
|
|
|
|
func (s *RepoService) Delete(ctx context.Context, id int64) error {
|
|
return s.store.DeleteRepo(ctx, id)
|
|
}
|