diff --git a/cmd/frontend/graphqlbackend/external_service.go b/cmd/frontend/graphqlbackend/external_service.go index da5f0c475b92..36baeed9082d 100644 --- a/cmd/frontend/graphqlbackend/external_service.go +++ b/cmd/frontend/graphqlbackend/external_service.go @@ -64,6 +64,7 @@ var availabilityCheck = map[string]bool{ extsvc.KindBitbucketServer: true, extsvc.KindBitbucketCloud: true, extsvc.KindAzureDevOps: true, + extsvc.KindPerforce: true, } func externalServiceByID(ctx context.Context, db database.DB, gqlID graphql.ID) (*externalServiceResolver, error) { diff --git a/cmd/gitserver/server/vcs_syncer_perforce.go b/cmd/gitserver/server/vcs_syncer_perforce.go index b9b1c4ba250b..dcf5b39c9531 100644 --- a/cmd/gitserver/server/vcs_syncer_perforce.go +++ b/cmd/gitserver/server/vcs_syncer_perforce.go @@ -67,6 +67,10 @@ func (s *PerforceDepotSyncer) Type() string { return "perforce" } +func (s *PerforceDepotSyncer) CanConnect(ctx context.Context, host, username, password string) error { + return p4testWithTrust(ctx, host, username, password) +} + // IsCloneable checks to see if the Perforce remote URL is cloneable. func (s *PerforceDepotSyncer) IsCloneable(ctx context.Context, remoteURL *vcs.URL) error { username, password, host, path, err := decomposePerforceRemoteURL(remoteURL) diff --git a/internal/repos/perforce.go b/internal/repos/perforce.go index dabf50dde0f1..3951b3de0249 100644 --- a/internal/repos/perforce.go +++ b/internal/repos/perforce.go @@ -10,6 +10,7 @@ import ( "github.com/sourcegraph/sourcegraph/internal/conf/reposource" "github.com/sourcegraph/sourcegraph/internal/extsvc" "github.com/sourcegraph/sourcegraph/internal/extsvc/perforce" + "github.com/sourcegraph/sourcegraph/internal/gitserver" "github.com/sourcegraph/sourcegraph/internal/jsonc" "github.com/sourcegraph/sourcegraph/internal/types" "github.com/sourcegraph/sourcegraph/internal/vcs" @@ -45,10 +46,19 @@ func newPerforceSource(svc *types.ExternalService, c *schema.PerforceConnection) }, nil } -// CheckConnection at this point assumes availability and relies on errors returned -// from the subsequent calls. This is going to be expanded as part of issue #44683 -// to actually only return true if the source can serve requests. +// CheckConnection tests the code host connection to make sure it works. +// For Perforce, it uses the host (p4.port), username (p4.user) and password (p4.passwd) +// from the code host configuration. func (s PerforceSource) CheckConnection(ctx context.Context) error { + // since CheckConnection is called from the frontend, we can't rely on the `p4` executable + // being available, so we need to make an RPC call to `gitserver`, where it is available. + // Use what is for us a "no-op" `p4` command that should always succeed. + gclient := gitserver.NewClient() + rc, _, err := gclient.P4Exec(ctx, s.config.P4Port, s.config.P4User, s.config.P4Passwd, "users") + if err != nil { + return errors.Wrap(err, "Unable to connect to the Perforce server") + } + rc.Close() return nil } @@ -85,12 +95,15 @@ func (s PerforceSource) ListRepos(ctx context.Context, results chan SourceResult // composePerforceCloneURL composes a clone URL for a Perforce depot based on // given information. e.g. // perforce://ssl:111.222.333.444:1666//Sourcegraph/ -func composePerforceCloneURL(host, depot string) string { +func composePerforceCloneURL(host, depot, username, password string) string { cloneURL := url.URL{ Scheme: "perforce", Host: host, Path: depot, } + if username != "" && password != "" { + cloneURL.User = url.UserPassword(username, password) + } return cloneURL.String() } @@ -101,7 +114,7 @@ func (s PerforceSource) makeRepo(depot string) *types.Repo { name := strings.Trim(depot, "/") urn := s.svc.URN() - cloneURL := composePerforceCloneURL(s.config.P4Port, depot) + cloneURL := composePerforceCloneURL(s.config.P4Port, depot, "", "") return &types.Repo{ Name: reposource.PerforceRepoName(