diff options
Diffstat (limited to 'internal/admin/model.go')
| -rw-r--r-- | internal/admin/model.go | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/internal/admin/model.go b/internal/admin/model.go new file mode 100644 index 0000000..59f2498 --- /dev/null +++ b/internal/admin/model.go @@ -0,0 +1,194 @@ +// Package admin manage repos +package admin + +import ( + "errors" + "fmt" + "io" + "io/fs" + "log" + "os" + "path/filepath" + + "gopkg.in/yaml.v2" + + "gopkg.in/ini.v1" +) + +const ( + // Read mode operations for repo + Read Action = 0 + // Write mode operations for repo + Write = 1 + // Admin mode operations for repo + Admin = 2 + // GitExportMagic magic file name for daemon export + GitExportMagic = "git-daemon-export-ok" +) + +// Action composite type for modes +type Action int + +// GitWeb git web configuration +type GitWeb struct { + Owner string `json:"owner"` + Description string `json:"description"` + Category string `json:"category"` + URL string `json:"url"` +} + +// Permission authorization controls +type Permission struct { + Role string `json:"role"` + Mode Action `json:"mode"` +} + +// GitRepo git repository +type GitRepo struct { + // Public "git-daemon-export-ok" magic file for git-http-backend + Public bool `json:"public"` + // Name game of repository + Name string `json:"name"` + // Web config settings + GitWebConfig *GitWeb `json:"git_web_config"` + // Permissions for authorization + Permissions []*Permission `json:"permissions"` +} + +// ServerRepos repos that are part of this server instance +type ServerRepos struct { + Name string `json:"name"` + Version string `json:"version"` + Repos []*GitRepo `json:"repos"` + BasePath string `json:"basepath"` +} + +func loadServerConfig(configPath string) *ServerRepos { + file, err := os.Open(configPath) + if err != nil { + log.Fatalf("Failed to open gitserver config %s", err) + } + defer file.Close() + b, err := io.ReadAll(file) + if err != nil { + log.Fatalf("Failed to read the gitserver config %s", err) + } + config := &ServerRepos{} + err = yaml.Unmarshal(b, &config) + if err != nil { + log.Fatalf("Failed to parse gitserver config %s", err) + } + return config +} + +// ServerPolicies generate casbin policies +func (s *ServerRepos) ServerPolicies() [][]string { + policies := [][]string{} + for _, repo := range s.Repos { + policies = append(policies, repo.CasbinPolicies()...) + } + return policies +} + +// ConfigureRepos run reconciler for all repos +func (s *ServerRepos) ConfigureRepos() { + for _, repo := range s.Repos { + repo.ReconcileRepo(s.BasePath) + } +} + +func readOnlyPaths(role, repoName string) [][]string { + return [][]string{ + []string{role, fmt.Sprintf("/%s/info/refs", repoName), "GET"}, + []string{role, fmt.Sprintf("/%s/git-upload-pack", repoName), "POST"}, + } +} +func writePaths(role, repoName string) [][]string { + return [][]string{[]string{role, fmt.Sprintf("/%s/git-recieve-pack", repoName), "POST"}} +} + +// Policy generate policy for repo base on mode +func (p *Permission) Policy(repoName string) [][]string { + policies := [][]string{} + // if read mode or greater e.g. write mode + if p.Mode >= Read { + policies = append(policies, readOnlyPaths(p.Role, repoName)...) + } + // if write mode + if p.Mode >= Write { + policies = append(policies, writePaths(p.Role, repoName)...) + } + return policies +} + +// CasbinPolicies generate all policies +func (r *GitRepo) CasbinPolicies() [][]string { + policies := [][]string{} + for _, perm := range r.Permissions { + policies = append(policies, perm.Policy(r.Name)...) + } + return policies +} + +// ReconcileRepo update repo export settings, update web config +func (r *GitRepo) ReconcileRepo(basePath string) { + // if exist -> continue + repoBase := filepath.Join(basePath, r.Name, ".git") + _, err := os.Stat(repoBase) + if errors.Is(err, fs.ErrNotExist) { + // if no exist -> init bare + } + r.ConfigureExport(repoBase) + + if r.GitWebConfig == nil { + r.GitWebConfig = &GitWeb{} + } + r.GitWebConfig.ReconcileGitConf(repoBase) +} + +// ConfigureExport setup repo for sharing and configure web settings +func (r *GitRepo) ConfigureExport(basePath string) { + // do nothing on public repos + repoBase := fmt.Sprintf("%s.git", r.Name) + okExport := filepath.Join(repoBase, r.Name, GitExportMagic) + _, err := os.Stat(okExport) + // Not public but the export setting is setting exists + if !r.Public && err == nil { + // delete file + os.Remove(okExport) + return + } + // Not public and the file doesn't exist + if !r.Public && errors.Is(err, fs.ErrNotExist) { + return + } + // + f, err := os.Create(okExport) + defer f.Close() + if err != nil { + log.Fatalf("git-daemon-export-ok coudln't be created %s", err) + return + } +} + +// ReconcileGitConf reconcile gitweb configuration section of gitconfig +func (r *GitWeb) ReconcileGitConf(repoBase string) { + confPath := filepath.Join(repoBase, "conf") + cfg, err := ini.Load(confPath) + if err != nil { + log.Fatal("Coudln't read gitconfig") + } + // check if empty, delete + if (GitWeb{} == *r) { + if cfg.HasSection("gitweb") { + cfg.DeleteSection("gitweb") + } + return + } + section := cfg.Section("gitweb") + section.Key("description").SetValue(r.Description) + section.Key("owner").SetValue(r.Owner) + section.Key("url").SetValue(r.URL) + section.Key("category").SetValue(r.Category) + cfg.SaveTo(confPath) +} |