diff --git a/server/api/login.go b/server/api/login.go index ef9df7be5..50de2429e 100644 --- a/server/api/login.go +++ b/server/api/login.go @@ -144,7 +144,9 @@ func HandleAuth(c *gin.Context) { Page: page, PerPage: perPage, }) - if terr != nil { + if errors.Is(terr, forge_types.ErrNotImplemented) { + log.Debug().Msg("Could not fetch membership of user as forge adapter did not implement it") + } else if terr != nil { log.Error().Err(terr).Msgf("cannot verify team membership for %s", userFromForge.Login) c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=internal_error") return diff --git a/server/api/repo.go b/server/api/repo.go index 12d99eaef..21224bc25 100644 --- a/server/api/repo.go +++ b/server/api/repo.go @@ -396,7 +396,9 @@ func GetRepoBranches(c *gin.Context) { forge.Refresh(c, _forge, _store, repoUser) branches, err := _forge.Branches(c, repoUser, repo, session.Pagination(c)) - if err != nil { + if errors.Is(err, forge_types.ErrNotImplemented) { + log.Debug().Msg("Could not fetch repo branch list as forge adapter did not implement it") + } else if err != nil { log.Error().Err(err).Msg("failed to load branches") c.String(http.StatusInternalServerError, "failed to load branches: %s", err) return @@ -435,7 +437,9 @@ func GetRepoPullRequests(c *gin.Context) { forge.Refresh(c, _forge, _store, repoUser) prs, err := _forge.PullRequests(c, repoUser, repo, session.Pagination(c)) - if err != nil { + if errors.Is(err, forge_types.ErrNotImplemented) { + log.Debug().Msg("Could not fetch repo pull-request list as forge adapter did not implement it") + } else if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } diff --git a/server/cache/membership.go b/server/cache/membership.go index 0e7138eaa..cf1e09390 100644 --- a/server/cache/membership.go +++ b/server/cache/membership.go @@ -16,12 +16,15 @@ package cache import ( "context" + "errors" "fmt" "time" "github.com/jellydator/ttlcache/v3" + "github.com/rs/zerolog/log" "go.woodpecker-ci.org/woodpecker/v3/server/forge" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" "go.woodpecker-ci.org/woodpecker/v3/server/model" "go.woodpecker-ci.org/woodpecker/v3/server/store" ) @@ -56,7 +59,10 @@ func (c *membershipCache) Get(ctx context.Context, _forge forge.Forge, u *model. } perm, err := _forge.OrgMembership(ctx, u, org) - if err != nil { + if errors.Is(err, forge_types.ErrNotImplemented) { + log.Debug().Msg("Could not check user org membership as forge adapter did not implement it") + return &model.OrgPerm{}, nil + } else if err != nil { return nil, err } c.cache.Set(key, perm, c.ttl) diff --git a/server/forge/forge.go b/server/forge/forge.go index fb5adb474..3f322e178 100644 --- a/server/forge/forge.go +++ b/server/forge/forge.go @@ -51,6 +51,7 @@ import ( // Error Handling: // - types.ErrIgnoreEvent: Skippable webhook events // - types.RecordNotExist: Resource not found +// - types.ErrNotImplemented: Can be used to signal it's not supported // - nil Repo/Pipeline: "No action needed" (not an error). type Forge interface { // Name returns the unique identifier of this forge driver. @@ -76,9 +77,11 @@ type Forge interface { Auth(ctx context.Context, token, secret string) (string, error) // Teams fetches all team/organization memberships for a user. - // May return empty slice if forge doesn't support teams/organizations. // Used to determine if an user is member of an team/organization. // Should support pagination via ListOptions. + // + // Errors: + // - Expect types.ErrNotImplemented to be returned if forge doesn't support teams/organizations. Teams(ctx context.Context, u *model.User, p *model.ListOptions) ([]*model.Team, error) // Repo fetches a single repository. @@ -105,6 +108,9 @@ type Forge interface { // Dir fetches all files in a directory at a specific commit. // Supports pipeline configurations split across multiple files. // Should return files only. + // + // Errors: + // - Expect types.ErrNotImplemented to be returned if not supported by the forge Dir(ctx context.Context, u *model.User, r *model.Repo, b *model.Pipeline, dirName string) ([]*types.FileMeta, error) // Status sends workflow status updates to the forge. @@ -128,13 +134,20 @@ type Forge interface { // Branches returns all branch names in the repository. // Should support pagination via ListOptions. + // + // Errors: + // - Expect types.ErrNotImplemented to be returned if not supported by the forge Branches(ctx context.Context, u *model.User, r *model.Repo, p *model.ListOptions) ([]string, error) // BranchHead returns the latest commit SHA for a branch. + // Is essential for cron feature to work. BranchHead(ctx context.Context, u *model.User, r *model.Repo, branch string) (*model.Commit, error) // PullRequests returns all open pull requests. // Should support pagination via ListOptions. + // + // Errors: + // - Expect types.ErrNotImplemented to be returned if not supported by the forge PullRequests(ctx context.Context, u *model.User, r *model.Repo, p *model.ListOptions) ([]*model.PullRequest, error) // Hook parses incoming webhook and returns pipeline data. @@ -157,6 +170,9 @@ type Forge interface { // OrgMembership checks if user is member of organization and their permission. // Should return (Member: false, Admin: false) if not a member. + // + // Errors: + // - Expect types.ErrNotImplemented to be returned if not supported by the forge OrgMembership(ctx context.Context, u *model.User, org string) (*model.OrgPerm, error) // Org fetches organization details. diff --git a/server/services/config/forge.go b/server/services/config/forge.go index ec76c8346..57bf040e8 100644 --- a/server/services/config/forge.go +++ b/server/services/config/forge.go @@ -146,7 +146,9 @@ func (f *forgeFetcherContext) getFirstAvailableConfig(c context.Context, configs files, err := f.forge.Dir(c, f.user, f.repo, f.pipeline, strings.TrimSuffix(fileOrFolder, "/")) // if folder is not supported we will get a "Not implemented" error and continue if err != nil { - if !errors.Is(err, types.ErrNotImplemented) && !errors.Is(err, &types.ErrConfigNotFound{}) { + if errors.Is(err, types.ErrNotImplemented) { + log.Debug().Msg("Could not fetch config folder as forge adapter did not implement it") + } else if !errors.Is(err, &types.ErrConfigNotFound{}) { log.Error().Err(err).Str("repo", f.repo.FullName).Str("user", f.user.Login).Msgf("could not get folder from forge: %s", err) forgeErr = append(forgeErr, err) }