aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/model.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--internal/admin/model.go194
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)
+}