aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorMax Resnick <max@ofmax.li>2024-05-04 10:24:48 -0700
committerMax Resnick <max@ofmax.li>2024-05-20 21:33:57 -0700
commitb8584fec70bd6810fa2392deadc69595b3e94cfa (patch)
tree2c2b17e06f780ad7d6ba46a0189f4faf45629fcb /internal
parent11fe503d5ea17fa3ddc4ce2308ea82b6edc7763e (diff)
downloadgo-git-server-b8584fec70bd6810fa2392deadc69595b3e94cfa.tar.gz
feat: refactor of repo management
Diffstat (limited to 'internal')
-rw-r--r--internal/admin/middleware.go6
-rw-r--r--internal/admin/middleware_test.go107
-rw-r--r--internal/admin/model.go48
-rw-r--r--internal/admin/model_test.go91
-rw-r--r--internal/admin/service.go11
-rw-r--r--internal/admin/service_test.go40
-rw-r--r--internal/authz/middleware.go2
7 files changed, 219 insertions, 86 deletions
diff --git a/internal/admin/middleware.go b/internal/admin/middleware.go
index 8b88c83..0dcf558 100644
--- a/internal/admin/middleware.go
+++ b/internal/admin/middleware.go
@@ -1,13 +1,17 @@
package admin
import (
+ "fmt"
"net/http"
)
// Hooks middleware to handle requests to the admin repo.
func Hooks(adminSvc *Servicer, next http.Handler) http.Handler {
+ repoUpdatePath := fmt.Sprintf("/%s/git-receive-pack", mgmtRepoName)
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
- go adminSvc.Reload()
next.ServeHTTP(rw, req)
+ if req.URL.Path == repoUpdatePath && req.Method == http.MethodPost {
+ adminSvc.Reload()
+ }
})
}
diff --git a/internal/admin/middleware_test.go b/internal/admin/middleware_test.go
new file mode 100644
index 0000000..a50d02d
--- /dev/null
+++ b/internal/admin/middleware_test.go
@@ -0,0 +1,107 @@
+package admin
+
+import (
+ "bytes"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/go-git/go-billy/v5/util"
+ "github.com/go-git/go-git/v5"
+ "github.com/go-git/go-git/v5/plumbing/object"
+ "sigs.k8s.io/yaml"
+)
+
+func junkTestHandler() http.HandlerFunc {
+ return func(rw http.ResponseWriter, req *http.Request) {
+ rw.WriteHeader(http.StatusOK)
+ _, err := rw.Write([]byte("Im a body"))
+ if err != nil {
+ log.Fatalf("couldn't write http body %s", err)
+ }
+ }
+}
+
+func TestHooks(t *testing.T) {
+ tempDir := t.TempDir()
+ tempRepoDir := filepath.Join(tempDir, "mgmt.git")
+ err := os.Mkdir(tempRepoDir, 0750)
+ if err != nil && !os.IsExist(err) {
+ log.Fatal(err)
+ }
+
+ // setup server with repo for management
+ defaultConfigBytes, err := yaml.Marshal(defaultServerConfig)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := configDefaultTestRepo(tempRepoDir, defaultConfigBytes); err != nil {
+ t.Fatal(err)
+ }
+ destModelFile, destPolicyFile, _ := tempModelPolicyConfig(t, tempDir, tempRepoDir)
+ // Load the server/config from the repo
+ adminService, err := NewService(destModelFile,
+ destPolicyFile,
+ "gitserver.yaml",
+ tempDir,
+ true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ repo, err := git.PlainOpen(tempRepoDir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ worktree, err := repo.Worktree()
+ if err != nil {
+ t.Fatal("couldn't get working tree", err)
+ }
+ fileToCommit := "gitserver.yaml"
+ newTestConfigRepo := *defaultRepo
+ newTestConfigRepo.Name = "mymiddlewaretest"
+ newServerConfig := *defaultServerConfig
+ newServerConfig.Repos = append(newServerConfig.Repos, &newTestConfigRepo)
+
+ newConfigBytes, err := yaml.Marshal(newServerConfig)
+ if err != nil {
+ t.Fatal("couldn't marshal new server config", err)
+ }
+ err = util.WriteFile(worktree.Filesystem, fileToCommit, newConfigBytes, os.FileMode(int(0644)))
+ if err != nil {
+ t.Fatal("couldn't write server config", err)
+ }
+ if _, err := worktree.Add(fileToCommit); err != nil {
+ t.Fatal("couldn't add server config to worktree", err)
+ }
+ _, err = worktree.Commit(fileToCommit,
+ &git.CommitOptions{
+ Author: &object.Signature{
+ Name: "go git server",
+ Email: "go-git-server@go-git-server",
+ },
+ },
+ )
+ if err != nil {
+ t.Fatal("couldn't commit file", err)
+ }
+ req := httptest.NewRequest(http.MethodPost, "http://localhost:3456/mgmt.git/git-receive-pack", bytes.NewBuffer([]byte("stuff")))
+ recorder := httptest.NewRecorder()
+ adminHandler := Hooks(adminService, junkTestHandler())
+ adminHandler.ServeHTTP(recorder, req)
+ result := recorder.Result()
+ defer result.Body.Close()
+ found := false
+ for _, v := range adminService.Conf.Repos {
+ if v.Name == newTestConfigRepo.Name {
+ found = true
+ }
+ }
+ if !found {
+ t.Error("didn't find expected repo in config")
+ t.Fail()
+ }
+}
diff --git a/internal/admin/model.go b/internal/admin/model.go
index ee65045..079496e 100644
--- a/internal/admin/model.go
+++ b/internal/admin/model.go
@@ -97,19 +97,38 @@ type ServerRepos struct {
basePath string
}
-func loadFromGit(gitURL, filePath string) ([]byte, error) {
+func loadConfigFromGit(baseDir, filePath string) ([]byte, error) {
+ mgmtPath := filepath.Join(baseDir, mgmtRepoName)
+ _, err := os.Stat(mgmtPath)
+ if errors.Is(err, os.ErrNotExist) {
+ return []byte(""), ErrMgmtRepoNotFound
+ } else if err != nil {
+ log.Fatalf("An unexpected error was encountered %s", err)
+ }
+ repoURI := fmt.Sprintf("file://%s", mgmtPath)
+ slog.Info("mgmt repo uri", "uri", repoURI)
fs := memfs.New()
storer := memory.NewStorage()
- _, err := git.Clone(storer, fs, &git.CloneOptions{
- URL: gitURL,
+ _, err = git.Clone(storer, fs, &git.CloneOptions{
+ URL: repoURI,
})
if err != nil {
- return []byte(""), fmt.Errorf("couldn't clone mgmt repo %w", err)
+ return []byte(""), fmt.Errorf("couldn't clone mgmt repo %s %w", repoURI, err)
+ }
+ files, err := fs.ReadDir("/")
+ if err != nil {
+ log.Fatal(err)
+ }
+ for _, file := range files {
+ fmt.Println(file)
+
}
+
file, err := fs.Open(filePath)
if err != nil {
return []byte(""), fmt.Errorf("couldn't open file in repo %w", err)
}
+
defer file.Close()
return io.ReadAll(file)
}
@@ -155,24 +174,13 @@ func loadServerConfig(mgmtRepo bool, baseDir, configPath string) (*ServerRepos,
err error
)
if mgmtRepo {
- mgmtPath := filepath.Join(baseDir, mgmtRepoName)
- _, err := os.Stat(mgmtPath)
- if errors.Is(err, os.ErrNotExist) {
- return &ServerRepos{}, ErrMgmtRepoNotFound
- } else if err != nil {
- log.Fatalf("An unexpected error was encountered %s", err)
- }
- repoURI := filepath.Join("file:///", mgmtPath)
- configBytes, err = loadFromGit(repoURI, configPath)
+ configBytes, err = loadConfigFromGit(baseDir, configPath)
if err != nil {
- // log.error
- slog.Error("Failed to load config file from git", "path", configPath)
return &ServerRepos{}, err
}
} else {
configBytes, err = loadLocalFile(configPath)
if err != nil {
- // log.error
slog.Error("Local server config couldn't be loaded")
return &ServerRepos{}, err
}
@@ -203,13 +211,15 @@ func (s *ServerRepos) ConfigureRepos() error {
}
func readOnlyPaths(role, repoName string) [][]string {
+ // WARN this is kind of hard coded pathing... not sure that's great
return [][]string{
- []string{role, fmt.Sprintf("/%s/info/refs", repoName), "GET"},
- []string{role, fmt.Sprintf("/%s/git-upload-pack", repoName), "POST"},
+ {role, fmt.Sprintf("/%s/info/refs", repoName), "GET"},
+ {role, fmt.Sprintf("/%s/git-upload-pack", repoName), "POST"},
}
}
func writePaths(role, repoName string) [][]string {
- return [][]string{[]string{role, fmt.Sprintf("/%s/git-receive-pack", repoName), "POST"}}
+ // WARN this is kind of hard coded pathing... not sure that's great
+ return [][]string{{role, fmt.Sprintf("/%s/git-receive-pack", repoName), "POST"}}
}
// Policy generate policy for repo base on mode
diff --git a/internal/admin/model_test.go b/internal/admin/model_test.go
index b73280a..9690263 100644
--- a/internal/admin/model_test.go
+++ b/internal/admin/model_test.go
@@ -12,12 +12,48 @@ import (
"testing"
"github.com/go-git/go-billy/v5/osfs"
+ "github.com/go-git/go-billy/v5/util"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/storage/filesystem"
"gopkg.in/ini.v1"
+ "sigs.k8s.io/yaml"
)
+func configDefaultTestRepo(repoPath string, defaultConfigBytes []byte) error {
+ gitFs := osfs.New(repoPath)
+ strg := filesystem.NewStorage(gitFs, nil)
+ repo, _ := git.Init(strg, gitFs)
+ // add file
+
+ fileToCommit := "gitserver.yaml"
+ // src
+ err := util.WriteFile(gitFs, fileToCommit, defaultConfigBytes, os.FileMode(int(0644)))
+ if err != nil {
+ return err
+ }
+
+ wt, err := repo.Worktree()
+ if err != nil {
+ return err
+ }
+ if _, err := wt.Add(fileToCommit); err != nil {
+ return err
+ }
+ _, err = wt.Commit(fileToCommit,
+ &git.CommitOptions{
+ Author: &object.Signature{
+ Name: "go git server",
+ Email: "go-git-server@go-git-server",
+ },
+ },
+ )
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
//nolint:cyclop
func TestCasbinPolicies(t *testing.T) {
roleName := "myrole"
@@ -146,76 +182,37 @@ func TestMgmtGitConfig(t *testing.T) {
// setup tempdir
gitDir := t.TempDir()
// init git repo
- gitFs := osfs.New(gitDir)
- strg := filesystem.NewStorage(gitFs, nil)
- repo, _ := git.Init(strg, gitFs)
- // add file
-
- // src
- srcFile, err := os.Open("../../gitserver.yaml")
- if err != nil {
- t.Fatalf("Error opening base config %s", err)
- }
- defer srcFile.Close()
-
- // file name
- // fileToCommit := fs.Join(gitDir, "gitserver.yaml")
- fileToCommit := "gitserver.yaml"
-
- // dest
- destFile, err := gitFs.OpenFile(fileToCommit, os.O_RDWR|os.O_CREATE, 0755)
- if err != nil {
- t.Fatalf("failed to open destination in git repo %s", err)
- }
- defer destFile.Close()
-
- // copy
- if _, err := io.Copy(destFile, srcFile); err != nil {
- t.Fatalf("Error copying file %s", err)
- }
- // commit
- wt, err := repo.Worktree()
+ repoPath := filepath.Join(gitDir, mgmtRepoName)
+ defaultConfigBytes, err := yaml.Marshal(defaultServerConfig)
if err != nil {
t.Fatal(err)
}
- if _, err := wt.Add(fileToCommit); err != nil {
- t.Fatal(err)
- }
- _, err = wt.Commit(fileToCommit,
- &git.CommitOptions{
- Author: &object.Signature{
- Name: "go git server",
- Email: "go-git-server@go-git-server",
- },
- },
- )
- if err != nil {
- t.Fatalf("Error creating commit %s", err)
+ if err := configDefaultTestRepo(repoPath, defaultConfigBytes); err != nil {
+ t.Fatal(err)
}
// run load func
- content, err := loadFromGit(gitDir, "gitserver.yaml")
+ content, err := loadConfigFromGit(gitDir, "gitserver.yaml")
if err != nil {
t.Fatal(err)
}
// "go-git-server"
- if !bytes.Contains(content, []byte("go-git-server")) {
+ if !bytes.Equal(content, defaultConfigBytes) {
t.Fatal("config missing expected")
}
// check couldnt clone err
- _, err = loadFromGit("/dne/bar", "gitserver.yaml")
+ _, err = loadConfigFromGit("/dne/bar", "gitserver.yaml")
if err == nil {
t.Fatal("expected an cloning repo didn't find one")
}
// check couldnt open file err
- _, err = loadFromGit(gitDir, "dne.yaml")
+ _, err = loadConfigFromGit(gitDir, "dne.yaml")
if err == nil {
t.Fatal("expected an error opening config file didn't find one")
}
- // TODO run via serverLoadConfig
}
//nolint:cyclop
diff --git a/internal/admin/service.go b/internal/admin/service.go
index bcf42f9..c1082d5 100644
--- a/internal/admin/service.go
+++ b/internal/admin/service.go
@@ -26,8 +26,17 @@ func (s *Servicer) Reload() {
slog.Error("refusing to reload config")
return
}
+ oldConfig := s.Conf
s.Conf = tmpConfig
- _ = s.InitServer()
+ if err := s.InitServer(); err != nil {
+ slog.Error("couldn't init server with new config, falling back", err)
+ s.Conf = oldConfig
+ if err := s.InitServer(); err != nil {
+ slog.Error("couldn't init server with old config, falling back", err)
+ panic("new and old config couldn't init server, no available config to run")
+ }
+ slog.Error("server has fallen back to old config but it lives in memory only, in fragile state")
+ }
}
// InitServer initialize a git server and configure
diff --git a/internal/admin/service_test.go b/internal/admin/service_test.go
index 13a0007..06088bc 100644
--- a/internal/admin/service_test.go
+++ b/internal/admin/service_test.go
@@ -40,6 +40,28 @@ repos:
`)
)
+func tempModelPolicyConfig(t *testing.T, tempDir, tempRepoDir string) (string, string, string) {
+
+ // auth model
+ destModelFile := filepath.Join(tempDir, "auth_model.ini")
+ srcModelFile := "../../auth_model.ini"
+ copyFile(t, srcModelFile, destModelFile)
+ // end auth model
+
+ // policy
+ destPolicyFile := filepath.Join(tempDir, "testpolicy.csv")
+ srcPolicyFile := "../../tests/testpolicy.csv"
+ copyFile(t, srcPolicyFile, destPolicyFile)
+ // end policy
+
+ // config
+ destConfigFile := filepath.Join(tempRepoDir, "gitserver.yaml")
+ srcConfigFile := "../../gitserver.yaml"
+ copyFile(t, srcConfigFile, destConfigFile)
+ // end config
+ return destModelFile, destPolicyFile, destConfigFile
+}
+
func copyFile(t *testing.T, srcFilePath, destPath string) {
srcFile, err := os.Open(srcFilePath)
if err != nil {
@@ -64,23 +86,7 @@ func TestInitServer(t *testing.T) {
tempDir := t.TempDir()
tempRepoDir := t.TempDir()
- // auth model
- destModelFile := filepath.Join(tempDir, "auth_model.ini")
- srcModelFile := "../../auth_model.ini"
- copyFile(t, srcModelFile, destModelFile)
- // end auth model
-
- // policy
- destPolicyFile := filepath.Join(tempDir, "testpolicy.csv")
- srcPolicyFile := "../../tests/testpolicy.csv"
- copyFile(t, srcPolicyFile, destPolicyFile)
- // end policy
-
- // config
- destConfigFile := filepath.Join(tempRepoDir, "gitserver.yaml")
- srcConfigFile := "../../gitserver.yaml"
- copyFile(t, srcConfigFile, destConfigFile)
- // end config
+ destModelFile, destPolicyFile, destConfigFile := tempModelPolicyConfig(t, tempDir, tempRepoDir)
t.Run("test reload config success", func(t *testing.T) {
svc, _ := NewService(destModelFile,
diff --git a/internal/authz/middleware.go b/internal/authz/middleware.go
index f4838ec..f7e1728 100644
--- a/internal/authz/middleware.go
+++ b/internal/authz/middleware.go
@@ -64,7 +64,7 @@ func Authorization(adminSvc *admin.Servicer, next http.Handler) http.Handler {
action := req.Method
ok, err := adminSvc.Enforce(urn, repo, action)
if err != nil {
- slog.Info("error unning enforce", "error", err)
+ slog.Info("error running enforce", "error", err)
http.Error(rw, "Bad Request", http.StatusBadRequest)
return
}