add support for github orgs

This commit is contained in:
Massaki Archambault 2024-08-04 18:59:57 -04:00
parent abf8507673
commit 0ddc4e515e
6 changed files with 220 additions and 34 deletions

View File

@ -70,38 +70,41 @@ func (n *groupNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
} }
func (n *groupNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { func (n *groupNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
groups, repositories, _ := n.param.GitPlatform.FetchGroupContent(n.source.GetGroupID()) groups, repositories, err := n.param.GitPlatform.FetchGroupContent(n.source.GetGroupID())
if err != nil {
// Check if the map of groups contains it n.param.logger.Error(err.Error())
group, found := groups[name] } else {
if found { // Check if the map of groups contains it
attrs := fs.StableAttr{ group, found := groups[name]
Ino: group.GetGroupID(), if found {
Mode: fuse.S_IFDIR, attrs := fs.StableAttr{
Ino: group.GetGroupID(),
Mode: fuse.S_IFDIR,
}
groupNode, _ := newGroupNodeFromSource(group, n.param)
return n.NewInode(ctx, groupNode, attrs), 0
} }
groupNode, _ := newGroupNodeFromSource(group, n.param)
return n.NewInode(ctx, groupNode, attrs), 0
}
// Check if the map of projects contains it // Check if the map of projects contains it
repository, found := repositories[name] repository, found := repositories[name]
if found { if found {
attrs := fs.StableAttr{ attrs := fs.StableAttr{
Ino: repository.GetRepositoryID(), Ino: repository.GetRepositoryID(),
Mode: fuse.S_IFLNK, Mode: fuse.S_IFLNK,
}
repositoryNode, _ := newRepositoryNodeFromSource(repository, n.param)
return n.NewInode(ctx, repositoryNode, attrs), 0
} }
repositoryNode, _ := newRepositoryNodeFromSource(repository, n.param)
return n.NewInode(ctx, repositoryNode, attrs), 0
}
// Check if the map of static nodes contains it // Check if the map of static nodes contains it
staticNode, ok := n.staticNodes[name] staticNode, ok := n.staticNodes[name]
if ok { if ok {
attrs := fs.StableAttr{ attrs := fs.StableAttr{
Ino: staticNode.Ino(), Ino: staticNode.Ino(),
Mode: staticNode.Mode(), Mode: staticNode.Mode(),
}
return n.NewInode(ctx, staticNode, attrs), 0
} }
return n.NewInode(ctx, staticNode, attrs), 0
} }
return nil, syscall.ENOENT return nil, syscall.ENOENT

View File

@ -73,7 +73,7 @@ func main() {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
gitPlatformClient, _ = gitlab.NewClient(logger, loadedConfig.Gitlab.URL, loadedConfig.Gitlab.Token, *GitlabClientConfig) gitPlatformClient, _ = gitlab.NewClient(logger, *GitlabClientConfig)
} else if loadedConfig.FS.Platform == config.PlatformGithub { } else if loadedConfig.FS.Platform == config.PlatformGithub {
// Create the github client // Create the github client
GithubClientConfig, err := config.MakeGithubConfig(loadedConfig) GithubClientConfig, err := config.MakeGithubConfig(loadedConfig)

View File

@ -1,7 +1,9 @@
package github package github
import ( import (
"fmt"
"log/slog" "log/slog"
"sync"
"github.com/badjware/gitlabfs/config" "github.com/badjware/gitlabfs/config"
"github.com/badjware/gitlabfs/fstree" "github.com/badjware/gitlabfs/fstree"
@ -15,6 +17,11 @@ type githubClient struct {
logger *slog.Logger logger *slog.Logger
rootContent map[string]fstree.GroupSource rootContent map[string]fstree.GroupSource
// API response cache
organizationCacheMux sync.RWMutex
organizationNameToIDMap map[string]int64
organizationCache map[int64]*Organization
} }
func NewClient(logger *slog.Logger, config config.GithubClientConfig) (*githubClient, error) { func NewClient(logger *slog.Logger, config config.GithubClientConfig) (*githubClient, error) {
@ -28,15 +35,38 @@ func NewClient(logger *slog.Logger, config config.GithubClientConfig) (*githubCl
client: client, client: client,
logger: logger, logger: logger,
rootContent: nil,
organizationNameToIDMap: map[string]int64{},
organizationCache: map[int64]*Organization{},
} }
return gitHubClient, nil return gitHubClient, nil
} }
func (c *githubClient) FetchRootGroupContent() (map[string]fstree.GroupSource, error) { func (c *githubClient) FetchRootGroupContent() (map[string]fstree.GroupSource, error) {
return nil, nil if c.rootContent == nil {
rootContent := make(map[string]fstree.GroupSource)
for _, org_name := range c.GithubClientConfig.OrgNames {
org, err := c.fetchOrganization(org_name)
if err != nil {
c.logger.Warn(err.Error())
} else {
rootContent[org.Name] = org
}
}
// TODO: user + current user
c.rootContent = rootContent
}
return c.rootContent, nil
} }
func (c *githubClient) FetchGroupContent(gid uint64) (map[string]fstree.GroupSource, map[string]fstree.RepositorySource, error) { func (c *githubClient) FetchGroupContent(gid uint64) (map[string]fstree.GroupSource, map[string]fstree.RepositorySource, error) {
return nil, nil, nil if org, found := c.organizationCache[int64(gid)]; found {
return c.fetchOrganizationContent(org)
}
return nil, nil, fmt.Errorf("invalid gid: %v", gid)
} }

View File

@ -1 +1,104 @@
package github package github
import (
"context"
"fmt"
"sync"
"github.com/badjware/gitlabfs/fstree"
"github.com/google/go-github/v63/github"
)
type Organization struct {
ID int64
Name string
mux sync.Mutex
// hold org content
childRepositories map[string]fstree.RepositorySource
}
func (o *Organization) GetGroupID() uint64 {
return uint64(o.ID)
}
func (o *Organization) InvalidateContentCache() {
o.mux.Lock()
defer o.mux.Unlock()
// clear child repositories from cache
o.childRepositories = nil
}
func (c *githubClient) fetchOrganization(org_name string) (*Organization, error) {
c.organizationCacheMux.RLock()
cachedId, found := c.organizationNameToIDMap[org_name]
if found {
cachedOrg := c.organizationCache[cachedId]
c.organizationCacheMux.RUnlock()
// if found in cache, return the cached reference
c.logger.Debug("Organization cache hit", "org_name", org_name)
return cachedOrg, nil
} else {
c.organizationCacheMux.RUnlock()
c.logger.Debug("Organization cache miss", "org_name", org_name)
}
// If not found in cache, fetch organization infos from API
githubOrg, _, err := c.client.Organizations.Get(context.Background(), org_name)
if err != nil {
return nil, fmt.Errorf("failed to fetch organization with name %v: %v", org_name, err)
}
newOrg := Organization{
ID: *githubOrg.ID,
Name: *githubOrg.Login,
childRepositories: nil,
}
// save in cache
c.organizationCacheMux.Lock()
c.organizationCache[newOrg.ID] = &newOrg
c.organizationCacheMux.Unlock()
return &newOrg, nil
}
func (c *githubClient) fetchOrganizationContent(org *Organization) (map[string]fstree.GroupSource, map[string]fstree.RepositorySource, error) {
org.mux.Lock()
defer org.mux.Unlock()
// Get cached data if available
// TODO: cache cache invalidation?
if org.childRepositories == nil {
childRepositories := make(map[string]fstree.RepositorySource)
// Fetch the organization repositories
repositoryListOpt := &github.RepositoryListByOrgOptions{
ListOptions: github.ListOptions{PerPage: 100},
}
for {
githubRepositories, response, err := c.client.Repositories.ListByOrg(context.Background(), org.Name, repositoryListOpt)
if err != nil {
return nil, nil, fmt.Errorf("failed to fetch repository in github: %v", err)
}
for _, githubRepository := range githubRepositories {
repository := c.newRepositoryFromGithubRepository(githubRepository)
if repository != nil {
childRepositories[repository.Path] = repository
}
}
if response.NextPage == 0 {
break
}
// Get the next page
repositoryListOpt.Page = response.NextPage
}
org.childRepositories = childRepositories
}
return make(map[string]fstree.GroupSource), org.childRepositories, nil
}

View File

@ -0,0 +1,50 @@
package github
import (
"path"
"github.com/badjware/gitlabfs/config"
"github.com/google/go-github/v63/github"
)
type Repository struct {
ID int64
Path string
CloneURL string
DefaultBranch string
}
func (r *Repository) GetRepositoryID() uint64 {
return uint64(r.ID)
}
func (r *Repository) GetCloneURL() string {
return r.CloneURL
}
func (r *Repository) GetDefaultBranch() string {
return r.DefaultBranch
}
func (c *githubClient) newRepositoryFromGithubRepository(repository *github.Repository) *Repository {
if c.ArchivedRepoHandling == config.ArchivedProjectIgnore && *repository.Archived {
return nil
}
r := Repository{
ID: *repository.ID,
Path: *repository.Name,
DefaultBranch: *repository.DefaultBranch,
}
if r.DefaultBranch == "" {
r.DefaultBranch = "master"
}
if c.PullMethod == config.PullMethodSSH {
r.CloneURL = *repository.SSHURL
} else {
r.CloneURL = *repository.CloneURL
}
if c.ArchivedRepoHandling == config.ArchivedProjectHide && *repository.Archived {
r.Path = path.Join(path.Dir(r.Path), "."+path.Base(r.Path))
}
return &r
}

View File

@ -28,17 +28,17 @@ type gitlabClient struct {
userCache map[int]*User userCache map[int]*User
} }
func NewClient(logger *slog.Logger, gitlabUrl string, gitlabToken string, p config.GitlabClientConfig) (*gitlabClient, error) { func NewClient(logger *slog.Logger, config config.GitlabClientConfig) (*gitlabClient, error) {
client, err := gitlab.NewClient( client, err := gitlab.NewClient(
gitlabToken, config.Token,
gitlab.WithBaseURL(gitlabUrl), gitlab.WithBaseURL(config.URL),
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create gitlab client: %v", err) return nil, fmt.Errorf("failed to create gitlab client: %v", err)
} }
gitlabClient := &gitlabClient{ gitlabClient := &gitlabClient{
GitlabClientConfig: p, GitlabClientConfig: config,
client: client, client: client,
logger: logger, logger: logger,