diff options
| author | Max Resnick <max@ofmax.li> | 2024-05-04 10:24:48 -0700 |
|---|---|---|
| committer | Max Resnick <max@ofmax.li> | 2024-05-20 21:33:57 -0700 |
| commit | b8584fec70bd6810fa2392deadc69595b3e94cfa (patch) | |
| tree | 2c2b17e06f780ad7d6ba46a0189f4faf45629fcb /internal | |
| parent | 11fe503d5ea17fa3ddc4ce2308ea82b6edc7763e (diff) | |
| download | go-git-server-b8584fec70bd6810fa2392deadc69595b3e94cfa.tar.gz | |
feat: refactor of repo management
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/admin/middleware.go | 6 | ||||
| -rw-r--r-- | internal/admin/middleware_test.go | 107 | ||||
| -rw-r--r-- | internal/admin/model.go | 48 | ||||
| -rw-r--r-- | internal/admin/model_test.go | 91 | ||||
| -rw-r--r-- | internal/admin/service.go | 11 | ||||
| -rw-r--r-- | internal/admin/service_test.go | 40 | ||||
| -rw-r--r-- | internal/authz/middleware.go | 2 |
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 } |