diff --git a/internal/database/external_services.go b/internal/database/external_services.go index 1cbf4aebbb9..d0d22f900e7 100644 --- a/internal/database/external_services.go +++ b/internal/database/external_services.go @@ -380,6 +380,13 @@ func (e *ExternalServiceStore) validatePerforceConnection(ctx context.Context, i for _, validate := range e.PerforceValidators { err = multierror.Append(err, validate(c)) } + + if c.Depots == nil { + err = multierror.Append(err, errors.New("depots must be set")) + } + + err = multierror.Append(err, e.validateDuplicateRateLimits(ctx, id, extsvc.KindPerforce, c)) + return err.ErrorOrNil() } diff --git a/internal/database/repos_db_test.go b/internal/database/repos_db_test.go index 70136856a0c..532060e0001 100644 --- a/internal/database/repos_db_test.go +++ b/internal/database/repos_db_test.go @@ -1812,7 +1812,7 @@ func TestRepos_ListRepoNames_externalRepoPrefixes(t *testing.T) { svc := &types.ExternalService{ Kind: extsvc.KindPerforce, DisplayName: "Perforce - Test", - Config: `{"p4.port": "ssl:111.222.333.444:1666", "p4.user": "admin", "p4.passwd": "pa$$word", "repositoryPathPattern": "perforce/{depot}"}`, + Config: `{"p4.port": "ssl:111.222.333.444:1666", "p4.user": "admin", "p4.passwd": "pa$$word", "depots": [], "repositoryPathPattern": "perforce/{depot}"}`, } if err := ExternalServices(db).Create(ctx, confGet, svc); err != nil { t.Fatal(err) diff --git a/internal/extsvc/types.go b/internal/extsvc/types.go index 5120cbb04b9..cb514ec87f2 100644 --- a/internal/extsvc/types.go +++ b/internal/extsvc/types.go @@ -408,6 +408,14 @@ func GetLimitFromConfig(kind string, config interface{}) (rlc RateLimitConfig, e rlc.IsDefault = false } rlc.BaseURL = c.Url + case *schema.PerforceConnection: + // 2/s is the default limit we enforce + rlc.Limit = rate.Limit(5000.0 / 3600.0) + if c != nil && c.RateLimit != nil { + rlc.Limit = limitOrInf(c.RateLimit.Enabled, c.RateLimit.RequestsPerHour) + rlc.IsDefault = false + } + rlc.BaseURL = c.P4Port default: return rlc, ErrRateLimitUnsupported{codehostKind: kind} } diff --git a/schema/perforce.schema.json b/schema/perforce.schema.json index f4e7ae83e19..f17c731e1f5 100644 --- a/schema/perforce.schema.json +++ b/schema/perforce.schema.json @@ -34,6 +34,29 @@ "default": 1000, "minimum": 1 }, + "rateLimit": { + "description": "Rate limit applied when making background API requests to Perforce.", + "title": "PerforceRateLimit", + "type": "object", + "required": ["enabled", "requestsPerHour"], + "properties": { + "enabled": { + "description": "true if rate limiting is enabled.", + "type": "boolean", + "default": true + }, + "requestsPerHour": { + "description": "Requests per hour permitted. This is an average, calculated per second.", + "type": "number", + "default": 5000, + "minimum": 0 + } + }, + "default": { + "enabled": true, + "requestsPerHour": 5000 + } + }, "authorization": { "title": "PerforceAuthorization", "description": "If non-null, enforces Perforce depot permissions.", diff --git a/schema/schema.go b/schema/schema.go index 86d41823ba1..03723871bac 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -1050,6 +1050,8 @@ type PerforceConnection struct { P4Port string `json:"p4.port"` // P4User description: The user to be authenticated for p4 CLI (P4USER). P4User string `json:"p4.user"` + // RateLimit description: Rate limit applied when making background API requests to Perforce. + RateLimit *PerforceRateLimit `json:"rateLimit,omitempty"` // RepositoryPathPattern description: The pattern used to generate the corresponding Sourcegraph repository name for a Perforce depot. In the pattern, the variable "{depot}" is replaced with the Perforce depot's path. // // For example, if your Perforce depot path is "//Sourcegraph/" and your Sourcegraph URL is https://src.example.com, then a repositoryPathPattern of "perforce/{depot}" would mean that the Perforce depot is available on Sourcegraph at https://src.example.com/perforce/Sourcegraph. @@ -1058,6 +1060,14 @@ type PerforceConnection struct { RepositoryPathPattern string `json:"repositoryPathPattern,omitempty"` } +// PerforceRateLimit description: Rate limit applied when making background API requests to Perforce. +type PerforceRateLimit struct { + // Enabled description: true if rate limiting is enabled. + Enabled bool `json:"enabled"` + // RequestsPerHour description: Requests per hour permitted. This is an average, calculated per second. + RequestsPerHour float64 `json:"requestsPerHour"` +} + // PermissionsUserMapping description: Settings for Sourcegraph permissions, which allow the site admin to explicitly manage repository permissions via the GraphQL API. This setting cannot be enabled if repository permissions for any specific external service are enabled (i.e., when the external service's `authorization` field is set). type PermissionsUserMapping struct { // BindID description: The type of identifier to identify a user. The default is "email", which uses the email address to identify a user. Use "username" to identify a user by their username. Changing this setting will erase any permissions created for users that do not yet exist.