mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 11:01:44 +00:00
feat/cody-gateway: use Enterprise Portal for actor/productsubscriptions (#62934)
Migrates Cody Gateway to use the new Enterprise Portal's "read-only" APIs. For the most part, this is an in-place replacement - a lot of the diff is in testing and minor changes. Some changes, such as the removal of model allowlists, were made down the PR stack in https://github.com/sourcegraph/sourcegraph/pull/62911. At a high level, we replace the data requested by `cmd/cody-gateway/internal/dotcom/operations.graphql` and replace it with Enterprise Portal RPCs: - `codyaccessv1.GetCodyGatewayAccess` - `codyaccessv1.ListCodyGatewayAccesses` Use cases that previously required retrieving the active license tags now: 1. Use the display name provided by the Cody Access API https://github.com/sourcegraph/sourcegraph/pull/62968 2. Depend on the connected Enterprise Portal dev instance to only return dev subscriptions https://github.com/sourcegraph/sourcegraph/pull/62966 Closes https://linear.app/sourcegraph/issue/CORE-98 Related to https://linear.app/sourcegraph/issue/CORE-135 (https://github.com/sourcegraph/sourcegraph/pull/62909, https://github.com/sourcegraph/sourcegraph/pull/62911) Related to https://linear.app/sourcegraph/issue/CORE-97 ## Local development This change also adds Enterprise Portal to `sg start dotcom`. For local development, we set up Cody Gateway to connect to Enterprise Portal such that zero configuration is needed - all the required secrets are sourced from the `sourcegrah-local-dev` GCP project automatically when you run `sg start dotcom`, and local Cody Gateway will talk to local Enterprise Portal to do the Enterprise subscriptions sync. This is actually an upgrade from the current experience where you need to provide Cody Gateway a Sourcegraph user access token to test Enterprise locally, though the Sourcegraph user access token is still required for the PLG actor source. The credential is configured in https://console.cloud.google.com/security/secret-manager/secret/SG_LOCAL_DEV_SAMS_CLIENT_SECRET/overview?project=sourcegraph-local-dev, and I've included documentation in the secret annotation about what it is for and what to do with it:  ## Rollout plan I will open PRs to set up the necessary configuration for Cody Gateway dev and prod. Once reviews taper down I'll cut an image from this branch and deploy it to Cody Gateway dev, and monitor it closely + do some manual testing. Once verified, I'll land this change and monitor a rollout to production. Cody Gateway dev SAMS client: https://github.com/sourcegraph/infrastructure/pull/6108 Cody Gateway prod SAMS client update (this one already exists): ``` accounts=> UPDATE idp_clients SET scopes = scopes || '["enterprise_portal::subscription::read", "enterprise_portal::codyaccess::read"]'::jsonb WHERE id = 'sams_cid_018ea062-479e-7342-9473-66645e616cbf'; UPDATE 1 accounts=> select name, scopes from idp_clients WHERE name = 'Cody Gateway (prod)'; name | scopes ---------------------+---------------------------------------------------------------------------------------------------------------------------------- Cody Gateway (prod) | ["openid", "profile", "email", "offline_access", "enterprise_portal::subscription::read", "enterprise_portal::codyaccess::read"] (1 row) ``` Configuring the target Enterprise Portal instances: https://github.com/sourcegraph/infrastructure/pull/6127 ## Test plan Start the new `dotcom` runset, now including Enterprise Portal, and observe logs from both `enterprise-portal` and `cody-gateway`: ``` sg start dotcom ``` I reused the test plan from https://github.com/sourcegraph/sourcegraph/pull/62911: set up Cody Gateway external dependency secrets, then set up an enterprise subscription + license with a high seat count (for a high quota), and force a Cody Gateway sync: ``` curl -v -H 'Authorization: bearer sekret' http://localhost:9992/-/actor/sync-all-sources ``` This should indicate the new sync against "local dotcom" fetches the correct number of actors and whatnot. Using the local enterprise subscription's access token, we run the QA test suite: ```sh $ bazel test --runs_per_test=2 --test_output=all //cmd/cody-gateway/qa:qa_test --test_env=E2E_GATEWAY_ENDPOINT=http://localhost:9992 --test_env=E2E_GATEWAY_TOKEN=$TOKEN INFO: Analyzed target //cmd/cody-gateway/qa:qa_test (0 packages loaded, 0 targets configured). INFO: From Testing //cmd/cody-gateway/qa:qa_test (run 1 of 2): ==================== Test output for //cmd/cody-gateway/qa:qa_test (run 1 of 2): PASS ================================================================================ INFO: From Testing //cmd/cody-gateway/qa:qa_test (run 2 of 2): ==================== Test output for //cmd/cody-gateway/qa:qa_test (run 2 of 2): PASS ================================================================================ INFO: Found 1 test target... Target //cmd/cody-gateway/qa:qa_test up-to-date: bazel-bin/cmd/cody-gateway/qa/qa_test_/qa_test Aspect @@rules_rust//rust/private:clippy.bzl%rust_clippy_aspect of //cmd/cody-gateway/qa:qa_test up-to-date (nothing to build) Aspect @@rules_rust//rust/private:rustfmt.bzl%rustfmt_aspect of //cmd/cody-gateway/qa:qa_test up-to-date (nothing to build) INFO: Elapsed time: 13.653s, Critical Path: 13.38s INFO: 7 processes: 1 internal, 6 darwin-sandbox. INFO: Build completed successfully, 7 total actions //cmd/cody-gateway/qa:qa_test PASSED in 11.7s Stats over 2 runs: max = 11.7s, min = 11.7s, avg = 11.7s, dev = 0.0s Executed 1 out of 1 test: 1 test passes. ```
This commit is contained in:
parent
e0a7f7e2b8
commit
7e9d8ec8dc
@ -75,7 +75,7 @@ func (a *Actor) IsEmpty() bool {
|
||||
|
||||
func (a *Actor) IsDotComActor() bool {
|
||||
// Corresponds to sourcegraph.com subscription ID, or using a dotcom access token
|
||||
return a != nil && (a.GetSource() == codygateway.ActorSourceProductSubscription && a.ID == "d3d2b638-d0a2-4539-a099-b36860b09819") || a.GetSource() == codygateway.ActorSourceDotcomUser
|
||||
return a != nil && (a.GetSource() == codygateway.ActorSourceEnterpriseSubscription && a.ID == "d3d2b638-d0a2-4539-a099-b36860b09819") || a.GetSource() == codygateway.ActorSourceDotcomUser
|
||||
}
|
||||
|
||||
type contextKey int
|
||||
|
||||
@ -64,7 +64,7 @@ func TestIsDotComActor(t *testing.T) {
|
||||
t.Run("true for dotcom subscription", func(t *testing.T) {
|
||||
actor := &Actor{
|
||||
ID: "d3d2b638-d0a2-4539-a099-b36860b09819",
|
||||
Source: FakeSource{codygateway.ActorSourceProductSubscription},
|
||||
Source: FakeSource{codygateway.ActorSourceEnterpriseSubscription},
|
||||
}
|
||||
require.True(t, actor.IsDotComActor())
|
||||
})
|
||||
@ -79,7 +79,7 @@ func TestIsDotComActor(t *testing.T) {
|
||||
t.Run("false for other subscription", func(t *testing.T) {
|
||||
actor := &Actor{
|
||||
ID: "other-sub-id",
|
||||
Source: FakeSource{codygateway.ActorSourceProductSubscription},
|
||||
Source: FakeSource{codygateway.ActorSourceEnterpriseSubscription},
|
||||
}
|
||||
require.False(t, actor.IsDotComActor())
|
||||
})
|
||||
|
||||
@ -9,20 +9,21 @@ go_library(
|
||||
visibility = ["//cmd/cody-gateway:__subpackages__"],
|
||||
deps = [
|
||||
"//cmd/cody-gateway/internal/actor",
|
||||
"//cmd/cody-gateway/internal/dotcom",
|
||||
"//internal/codygateway",
|
||||
"//internal/collections",
|
||||
"//internal/license",
|
||||
"//internal/licensing",
|
||||
"//internal/productsubscription",
|
||||
"//internal/trace",
|
||||
"//lib/enterpriseportal/codyaccess/v1:codyaccess",
|
||||
"//lib/enterpriseportal/subscriptions/v1:subscriptions",
|
||||
"//lib/errors",
|
||||
"@com_github_gregjones_httpcache//:httpcache",
|
||||
"@com_github_khan_genqlient//graphql",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
"@com_github_vektah_gqlparser_v2//gqlerror",
|
||||
"@io_opentelemetry_go_otel//attribute",
|
||||
"@io_opentelemetry_go_otel_trace//:trace",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//codes",
|
||||
"@org_golang_google_grpc//status",
|
||||
],
|
||||
)
|
||||
|
||||
@ -32,11 +33,14 @@ go_test(
|
||||
embed = [":productsubscription"],
|
||||
tags = [TAG_CODY_PRIME],
|
||||
deps = [
|
||||
"//cmd/cody-gateway/internal/dotcom",
|
||||
"//internal/codygateway",
|
||||
"//internal/collections",
|
||||
"//lib/enterpriseportal/codyaccess/v1:codyaccess",
|
||||
"@com_github_hexops_autogold_v2//:autogold",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
"@org_golang_google_protobuf//types/known/durationpb",
|
||||
"@org_golang_x_exp//maps",
|
||||
],
|
||||
)
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "enterpriseportal",
|
||||
srcs = ["enterpriseportal.go"],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor/productsubscription/enterpriseportal",
|
||||
visibility = ["//cmd/cody-gateway:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/env",
|
||||
"//internal/grpc/defaults",
|
||||
"//internal/grpc/grpcoauth",
|
||||
"//lib/errors",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_x_oauth2//:oauth2",
|
||||
],
|
||||
)
|
||||
@ -0,0 +1,39 @@
|
||||
package enterpriseportal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"github.com/sourcegraph/log"
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/env"
|
||||
"github.com/sourcegraph/sourcegraph/internal/grpc/defaults"
|
||||
"github.com/sourcegraph/sourcegraph/internal/grpc/grpcoauth"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
// Dial establishes a connection to the Enterprise Portal gRPC service with
|
||||
// the given configuration. The oauth2.TokenSource should provide SAMS credentials.
|
||||
func Dial(ctx context.Context, logger log.Logger, addr *url.URL, ts oauth2.TokenSource) (*grpc.ClientConn, error) {
|
||||
insecureTarget := addr.Scheme != "https"
|
||||
if insecureTarget && !env.InsecureDev {
|
||||
return nil, errors.New("insecure target Enterprise Portal used outside of dev mode")
|
||||
}
|
||||
creds := grpc.WithPerRPCCredentials(grpcoauth.TokenSource{TokenSource: ts})
|
||||
var opts []grpc.DialOption
|
||||
if insecureTarget {
|
||||
opts = defaults.DialOptions(logger, creds)
|
||||
} else {
|
||||
opts = defaults.ExternalDialOptions(logger, creds)
|
||||
}
|
||||
logger.Info("dialing Enterprise Portal gRPC service",
|
||||
log.String("host", addr.Host),
|
||||
log.Bool("insecureTarget", insecureTarget))
|
||||
conn, err := grpc.DialContext(ctx, addr.Host, opts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to connect to Enterprise Portal gRPC service at %s", addr.String())
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
@ -3,27 +3,27 @@ package productsubscription
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
"github.com/gregjones/httpcache"
|
||||
"github.com/sourcegraph/log"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/codygateway"
|
||||
"github.com/sourcegraph/sourcegraph/internal/collections"
|
||||
"github.com/sourcegraph/sourcegraph/internal/license"
|
||||
"github.com/sourcegraph/sourcegraph/internal/licensing"
|
||||
"github.com/sourcegraph/sourcegraph/internal/productsubscription"
|
||||
sgtrace "github.com/sourcegraph/sourcegraph/internal/trace"
|
||||
codyaccessv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/codyaccess/v1"
|
||||
subscriptionsv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/subscriptions/v1"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/dotcom"
|
||||
)
|
||||
|
||||
// SourceVersion should be bumped whenever the format of any cached data in this
|
||||
@ -45,14 +45,20 @@ type ListingCache interface {
|
||||
ListAllKeys() []string
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
log log.Logger
|
||||
cache ListingCache // cache is expected to be something with automatic TTL
|
||||
dotcom graphql.Client
|
||||
// EnterprisePortalClient defines the RPCs implemented by the Enterprise Portal
|
||||
// that this Source depends on. We declare our own interface to keep our
|
||||
// generated mock surface minimal, and our dependencies explicit.
|
||||
type EnterprisePortalClient interface {
|
||||
// codyaccessv1 RPCs
|
||||
GetCodyGatewayAccess(context.Context, *codyaccessv1.GetCodyGatewayAccessRequest, ...grpc.CallOption) (*codyaccessv1.GetCodyGatewayAccessResponse, error)
|
||||
ListCodyGatewayAccesses(context.Context, *codyaccessv1.ListCodyGatewayAccessesRequest, ...grpc.CallOption) (*codyaccessv1.ListCodyGatewayAccessesResponse, error)
|
||||
}
|
||||
|
||||
// internalMode, if true, indicates only dev and internal licenses may use
|
||||
// this Cody Gateway instance.
|
||||
internalMode bool
|
||||
type Source struct {
|
||||
log log.Logger
|
||||
cache ListingCache // cache is expected to be something with automatic TTL
|
||||
|
||||
enterprisePortal EnterprisePortalClient
|
||||
|
||||
concurrencyConfig codygateway.ActorConcurrencyLimitConfig
|
||||
}
|
||||
@ -61,19 +67,23 @@ var _ actor.Source = &Source{}
|
||||
var _ actor.SourceUpdater = &Source{}
|
||||
var _ actor.SourceSyncer = &Source{}
|
||||
|
||||
func NewSource(logger log.Logger, cache ListingCache, dotcomClient graphql.Client, internalMode bool, concurrencyConfig codygateway.ActorConcurrencyLimitConfig) *Source {
|
||||
func NewSource(
|
||||
logger log.Logger,
|
||||
cache ListingCache,
|
||||
enterprisePortal EnterprisePortalClient,
|
||||
concurrencyConfig codygateway.ActorConcurrencyLimitConfig,
|
||||
) *Source {
|
||||
return &Source{
|
||||
log: logger.Scoped("productsubscriptions"),
|
||||
cache: cache,
|
||||
dotcom: dotcomClient,
|
||||
log: logger.Scoped("productsubscriptions"),
|
||||
cache: cache,
|
||||
|
||||
internalMode: internalMode,
|
||||
enterprisePortal: enterprisePortal,
|
||||
|
||||
concurrencyConfig: concurrencyConfig,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) Name() string { return string(codygateway.ActorSourceProductSubscription) }
|
||||
func (s *Source) Name() string { return string(codygateway.ActorSourceEnterpriseSubscription) }
|
||||
|
||||
func (s *Source) Get(ctx context.Context, token string) (*actor.Actor, error) {
|
||||
if token == "" {
|
||||
@ -139,23 +149,30 @@ func (s *Source) Sync(ctx context.Context) (seen int, errs error) {
|
||||
syncLog := sgtrace.Logger(ctx, s.log)
|
||||
seenTokens := collections.NewSet[string]()
|
||||
|
||||
resp, err := dotcom.ListProductSubscriptions(ctx, s.dotcom)
|
||||
resp, err := s.enterprisePortal.ListCodyGatewayAccesses(ctx, &codyaccessv1.ListCodyGatewayAccessesRequest{
|
||||
// TODO(https://linear.app/sourcegraph/issue/CORE-134): Once the
|
||||
// Enterprise Portal supports pagination in its API responses we need to
|
||||
// update this callsite to make repeated requests with a continuation
|
||||
// token, etc. For now, we assume that we are fetching all licenses in
|
||||
// a single call.
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
syncLog.Warn("sync context cancelled")
|
||||
return seen, nil
|
||||
}
|
||||
return seen, errors.Wrap(err, "failed to list subscriptions from dotcom")
|
||||
return seen, errors.Wrap(err, "failed to list Enterprise subscriptions")
|
||||
}
|
||||
|
||||
for _, sub := range resp.Dotcom.ProductSubscriptions.Nodes {
|
||||
for _, token := range sub.SourcegraphAccessTokens {
|
||||
for _, access := range resp.GetAccesses() {
|
||||
for _, token := range access.GetAccessTokens() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return seen, ctx.Err()
|
||||
default:
|
||||
}
|
||||
act := newActor(s, token, sub.ProductSubscriptionState, s.internalMode, s.concurrencyConfig)
|
||||
|
||||
act := newActor(s, token.GetToken(), access, time.Now())
|
||||
data, err := json.Marshal(act)
|
||||
if err != nil {
|
||||
act.Logger(syncLog).Error("failed to marshal actor",
|
||||
@ -163,8 +180,8 @@ func (s *Source) Sync(ctx context.Context) (seen int, errs error) {
|
||||
errs = errors.Append(errs, err)
|
||||
continue
|
||||
}
|
||||
s.cache.Set(token, data)
|
||||
seenTokens.Add(token)
|
||||
s.cache.Set(token.GetToken(), data)
|
||||
seenTokens.Add(token.GetToken())
|
||||
seen++
|
||||
}
|
||||
}
|
||||
@ -198,27 +215,25 @@ func removeUnseenTokens(seen collections.Set[string], cache ListingCache, syncLo
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) checkAccessToken(ctx context.Context, token string) (*dotcom.CheckAccessTokenResponse, error) {
|
||||
resp, err := dotcom.CheckAccessToken(ctx, s.dotcom, token)
|
||||
func (s *Source) checkAccessToken(ctx context.Context, token string) (*codyaccessv1.CodyGatewayAccess, error) {
|
||||
resp, err := s.enterprisePortal.GetCodyGatewayAccess(ctx, &codyaccessv1.GetCodyGatewayAccessRequest{
|
||||
Query: &codyaccessv1.GetCodyGatewayAccessRequest_AccessToken{
|
||||
AccessToken: token,
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
return resp, nil
|
||||
return resp.GetAccess(), nil
|
||||
}
|
||||
|
||||
// Inspect the error to see if it's a list of GraphQL errors.
|
||||
gqlerrs, ok := err.(gqlerror.List)
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, gqlerr := range gqlerrs {
|
||||
if gqlerr.Extensions != nil && gqlerr.Extensions["code"] == productsubscription.GQLErrCodeProductSubscriptionNotFound {
|
||||
return nil, actor.ErrAccessTokenDenied{
|
||||
Source: s.Name(),
|
||||
Reason: "associated product subscription not found",
|
||||
}
|
||||
// Inspect the error to see if it's not-found error.
|
||||
if statusErr, ok := status.FromError(err); ok && statusErr.Code() == codes.NotFound {
|
||||
return nil, actor.ErrAccessTokenDenied{
|
||||
Source: s.Name(),
|
||||
Reason: "associated product subscription not found",
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
|
||||
return nil, errors.Wrap(err, "verifying token via Enterprise Portal")
|
||||
}
|
||||
|
||||
func (s *Source) fetchAndCache(ctx context.Context, token string) (*actor.Actor, error) {
|
||||
@ -226,14 +241,13 @@ func (s *Source) fetchAndCache(ctx context.Context, token string) (*actor.Actor,
|
||||
resp, checkErr := s.checkAccessToken(ctx, token)
|
||||
if checkErr != nil {
|
||||
// Generate a stateless actor so that we aren't constantly hitting the dotcom API
|
||||
act = newActor(s, token, dotcom.ProductSubscriptionState{}, s.internalMode, s.concurrencyConfig)
|
||||
act = newActor(s, token, &codyaccessv1.CodyGatewayAccess{}, time.Now())
|
||||
} else {
|
||||
act = newActor(
|
||||
s,
|
||||
token,
|
||||
resp.Dotcom.ProductSubscriptionByAccessToken.ProductSubscriptionState,
|
||||
s.internalMode,
|
||||
s.concurrencyConfig,
|
||||
resp,
|
||||
time.Now(),
|
||||
)
|
||||
}
|
||||
|
||||
@ -252,86 +266,73 @@ func (s *Source) fetchAndCache(ctx context.Context, token string) (*actor.Actor,
|
||||
|
||||
// getSubscriptionAccountName attempts to get the account name from the product
|
||||
// subscription. It returns an empty string if no account name is available.
|
||||
func getSubscriptionAccountName(s dotcom.ProductSubscriptionState) string {
|
||||
// 1. Check if the special "customer:" tag is present
|
||||
if s.ActiveLicense != nil && s.ActiveLicense.Info != nil {
|
||||
for _, tag := range s.ActiveLicense.Info.Tags {
|
||||
if strings.HasPrefix(tag, "customer:") {
|
||||
return strings.TrimPrefix(tag, "customer:")
|
||||
}
|
||||
func getSubscriptionAccountName(activeLicenseTags []string) string {
|
||||
// Check if the special "customer:" tag is present.
|
||||
for _, tag := range activeLicenseTags {
|
||||
if strings.HasPrefix(tag, "customer:") {
|
||||
return strings.TrimPrefix(tag, "customer:")
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Use the username of the account
|
||||
if s.Account != nil && s.Account.Username != "" {
|
||||
return s.Account.Username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// newActor creates an actor from Sourcegraph.com product subscription state.
|
||||
func newActor(source *Source, token string, s dotcom.ProductSubscriptionState, internalMode bool, concurrencyConfig codygateway.ActorConcurrencyLimitConfig) *actor.Actor {
|
||||
name := getSubscriptionAccountName(s)
|
||||
func newActor(
|
||||
source *Source,
|
||||
token string,
|
||||
s *codyaccessv1.CodyGatewayAccess,
|
||||
now time.Time,
|
||||
) *actor.Actor {
|
||||
name := s.GetSubscriptionDisplayName()
|
||||
if name == "" {
|
||||
name = s.Uuid
|
||||
name = s.GetSubscriptionId()
|
||||
}
|
||||
|
||||
// In internal mode, only allow dev and internal licenses.
|
||||
disallowedLicense := internalMode &&
|
||||
(s.ActiveLicense == nil || s.ActiveLicense.Info == nil ||
|
||||
!containsOneOf(s.ActiveLicense.Info.Tags, licensing.DevTag, licensing.InternalTag))
|
||||
|
||||
now := time.Now()
|
||||
a := &actor.Actor{
|
||||
Key: token,
|
||||
ID: s.Uuid,
|
||||
Key: token,
|
||||
|
||||
// Maintain consistency with existing non-prefixed IDs.
|
||||
ID: strings.TrimPrefix(s.GetSubscriptionId(), subscriptionsv1.EnterpriseSubscriptionIDPrefix),
|
||||
|
||||
Name: name,
|
||||
AccessEnabled: !disallowedLicense && !s.IsArchived && s.CodyGatewayAccess.Enabled,
|
||||
AccessEnabled: s.GetEnabled(),
|
||||
EndpointAccess: map[string]bool{
|
||||
"/v1/attribution": !disallowedLicense && !s.IsArchived,
|
||||
// Always enabled even if !s.GetEnabled(), to allow BYOK customers.
|
||||
"/v1/attribution": true,
|
||||
},
|
||||
RateLimits: map[codygateway.Feature]actor.RateLimit{},
|
||||
LastUpdated: &now,
|
||||
Source: source,
|
||||
}
|
||||
|
||||
if rl := s.CodyGatewayAccess.ChatCompletionsRateLimit; rl != nil {
|
||||
if rl := s.GetChatCompletionsRateLimit(); rl != nil {
|
||||
a.RateLimits[codygateway.FeatureChatCompletions] = actor.NewRateLimitWithPercentageConcurrency(
|
||||
int64(rl.Limit),
|
||||
time.Duration(rl.IntervalSeconds)*time.Second,
|
||||
rl.IntervalDuration.AsDuration(),
|
||||
[]string{"*"}, // allow all models that are allowlisted by Cody Gateway
|
||||
concurrencyConfig,
|
||||
source.concurrencyConfig,
|
||||
)
|
||||
}
|
||||
|
||||
if rl := s.CodyGatewayAccess.CodeCompletionsRateLimit; rl != nil {
|
||||
if rl := s.GetCodeCompletionsRateLimit(); rl != nil {
|
||||
a.RateLimits[codygateway.FeatureCodeCompletions] = actor.NewRateLimitWithPercentageConcurrency(
|
||||
int64(rl.Limit),
|
||||
time.Duration(rl.IntervalSeconds)*time.Second,
|
||||
rl.IntervalDuration.AsDuration(),
|
||||
[]string{"*"}, // allow all models that are allowlisted by Cody Gateway
|
||||
concurrencyConfig,
|
||||
source.concurrencyConfig,
|
||||
)
|
||||
}
|
||||
|
||||
if rl := s.CodyGatewayAccess.EmbeddingsRateLimit; rl != nil {
|
||||
if rl := s.GetEmbeddingsRateLimit(); rl != nil {
|
||||
a.RateLimits[codygateway.FeatureEmbeddings] = actor.NewRateLimitWithPercentageConcurrency(
|
||||
int64(rl.Limit),
|
||||
time.Duration(rl.IntervalSeconds)*time.Second,
|
||||
rl.IntervalDuration.AsDuration(),
|
||||
[]string{"*"}, // allow all models that are allowlisted by Cody Gateway
|
||||
// TODO: Once we split interactive and on-interactive, we want to apply
|
||||
// stricter limits here than percentage based for this heavy endpoint.
|
||||
concurrencyConfig,
|
||||
source.concurrencyConfig,
|
||||
)
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func containsOneOf(s []string, needles ...string) bool {
|
||||
for _, needle := range needles {
|
||||
if slices.Contains(s, needle) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -1,160 +1,173 @@
|
||||
package productsubscription
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hexops/autogold/v2"
|
||||
"github.com/sourcegraph/log"
|
||||
"github.com/sourcegraph/sourcegraph/internal/collections"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/maps"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/dotcom"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codygateway"
|
||||
"github.com/sourcegraph/sourcegraph/internal/collections"
|
||||
codyaccessv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/codyaccess/v1"
|
||||
)
|
||||
|
||||
func TestNewActor(t *testing.T) {
|
||||
concurrencyConfig := codygateway.ActorConcurrencyLimitConfig{
|
||||
Percentage: 50,
|
||||
Interval: 24 * time.Hour,
|
||||
}
|
||||
type args struct {
|
||||
s dotcom.ProductSubscriptionState
|
||||
devLicensesOnly bool
|
||||
access *codyaccessv1.CodyGatewayAccess
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantEnabled bool
|
||||
name string
|
||||
args args
|
||||
wantActor autogold.Value
|
||||
}{
|
||||
{
|
||||
name: "not dev only",
|
||||
name: "enabled, no embeddings",
|
||||
args: args{
|
||||
dotcom.ProductSubscriptionState{
|
||||
CodyGatewayAccess: dotcom.ProductSubscriptionStateCodyGatewayAccess{
|
||||
CodyGatewayAccessFields: dotcom.CodyGatewayAccessFields{
|
||||
Enabled: true,
|
||||
ChatCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsChatCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
CodeCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsCodeCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
&codyaccessv1.CodyGatewayAccess{
|
||||
SubscriptionId: "es_1234uuid",
|
||||
SubscriptionDisplayName: "My Subscription",
|
||||
Enabled: true,
|
||||
ChatCompletionsRateLimit: &codyaccessv1.CodyGatewayRateLimit{
|
||||
Limit: 10,
|
||||
IntervalDuration: durationpb.New(10 * time.Second),
|
||||
},
|
||||
CodeCompletionsRateLimit: &codyaccessv1.CodyGatewayRateLimit{
|
||||
Limit: 10,
|
||||
IntervalDuration: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
wantEnabled: true,
|
||||
wantActor: autogold.Expect(`{
|
||||
"key": "sekret_token",
|
||||
"id": "1234uuid",
|
||||
"name": "My Subscription",
|
||||
"accessEnabled": true,
|
||||
"endpointAccess": {
|
||||
"/v1/attribution": true
|
||||
},
|
||||
"rateLimits": {
|
||||
"chat_completions": {
|
||||
"allowedModels": [
|
||||
"*"
|
||||
],
|
||||
"limit": 10,
|
||||
"interval": 10000000000,
|
||||
"concurrentRequests": 4320000,
|
||||
"concurrentRequestsInterval": 86400000000000
|
||||
},
|
||||
"code_completions": {
|
||||
"allowedModels": [
|
||||
"*"
|
||||
],
|
||||
"limit": 10,
|
||||
"interval": 10000000000,
|
||||
"concurrentRequests": 4320000,
|
||||
"concurrentRequestsInterval": 86400000000000
|
||||
}
|
||||
},
|
||||
"lastUpdated": "2024-06-03T20:03:07-07:00"
|
||||
}`),
|
||||
},
|
||||
{
|
||||
name: "dev only, not a dev license",
|
||||
name: "enabled, only embeddings",
|
||||
args: args{
|
||||
dotcom.ProductSubscriptionState{
|
||||
CodyGatewayAccess: dotcom.ProductSubscriptionStateCodyGatewayAccess{
|
||||
CodyGatewayAccessFields: dotcom.CodyGatewayAccessFields{
|
||||
Enabled: true,
|
||||
ChatCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsChatCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
CodeCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsCodeCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
&codyaccessv1.CodyGatewayAccess{
|
||||
SubscriptionId: "es_1234uuid",
|
||||
SubscriptionDisplayName: "My Subscription",
|
||||
Enabled: true,
|
||||
EmbeddingsRateLimit: &codyaccessv1.CodyGatewayRateLimit{
|
||||
Limit: 10,
|
||||
IntervalDuration: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
wantEnabled: false,
|
||||
wantActor: autogold.Expect(`{
|
||||
"key": "sekret_token",
|
||||
"id": "1234uuid",
|
||||
"name": "My Subscription",
|
||||
"accessEnabled": true,
|
||||
"endpointAccess": {
|
||||
"/v1/attribution": true
|
||||
},
|
||||
"rateLimits": {
|
||||
"embeddings": {
|
||||
"allowedModels": [
|
||||
"*"
|
||||
],
|
||||
"limit": 10,
|
||||
"interval": 10000000000,
|
||||
"concurrentRequests": 4320000,
|
||||
"concurrentRequestsInterval": 86400000000000
|
||||
}
|
||||
},
|
||||
"lastUpdated": "2024-06-03T20:03:07-07:00"
|
||||
}`),
|
||||
},
|
||||
{
|
||||
name: "dev only, is a dev license",
|
||||
name: "disabled",
|
||||
args: args{
|
||||
dotcom.ProductSubscriptionState{
|
||||
CodyGatewayAccess: dotcom.ProductSubscriptionStateCodyGatewayAccess{
|
||||
CodyGatewayAccessFields: dotcom.CodyGatewayAccessFields{
|
||||
Enabled: true,
|
||||
ChatCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsChatCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
CodeCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsCodeCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ActiveLicense: &dotcom.ProductSubscriptionStateActiveLicenseProductLicense{
|
||||
Info: &dotcom.ProductSubscriptionStateActiveLicenseProductLicenseInfo{
|
||||
Tags: []string{"dev"},
|
||||
},
|
||||
},
|
||||
&codyaccessv1.CodyGatewayAccess{
|
||||
SubscriptionId: "es_1234uuid",
|
||||
SubscriptionDisplayName: "My Subscription",
|
||||
Enabled: false,
|
||||
},
|
||||
true,
|
||||
},
|
||||
wantEnabled: true,
|
||||
wantActor: autogold.Expect(`{
|
||||
"key": "sekret_token",
|
||||
"id": "1234uuid",
|
||||
"name": "My Subscription",
|
||||
"accessEnabled": false,
|
||||
"endpointAccess": {
|
||||
"/v1/attribution": true
|
||||
},
|
||||
"rateLimits": {},
|
||||
"lastUpdated": "2024-06-03T20:03:07-07:00"
|
||||
}`),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
act := newActor(nil, "", tt.args.s, tt.args.devLicensesOnly, concurrencyConfig)
|
||||
assert.Equal(t, act.AccessEnabled, tt.wantEnabled)
|
||||
now := time.Date(2024, 6, 3, 20, 3, 7, 0, time.FixedZone("PDT", -25200))
|
||||
act := newActor(&Source{
|
||||
concurrencyConfig: codygateway.ActorConcurrencyLimitConfig{
|
||||
Percentage: 50,
|
||||
Interval: 24 * time.Hour,
|
||||
},
|
||||
}, "sekret_token", tt.args.access, now)
|
||||
// Assert against JSON representation, because that's what we end
|
||||
// up caching.
|
||||
actData, err := json.MarshalIndent(act, "", " ")
|
||||
require.NoError(t, err)
|
||||
tt.wantActor.Equal(t, string(actData))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSubscriptionAccountName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mockUsername string
|
||||
mockTags []string
|
||||
wantName string
|
||||
name string
|
||||
mockTags []string
|
||||
wantName string
|
||||
}{
|
||||
{
|
||||
name: "has special license tag",
|
||||
mockUsername: "alice",
|
||||
mockTags: []string{"trial", "customer:acme"},
|
||||
wantName: "acme",
|
||||
name: "has special license tag",
|
||||
mockTags: []string{"trial", "customer:acme"},
|
||||
wantName: "acme",
|
||||
},
|
||||
{
|
||||
name: "use account username",
|
||||
mockUsername: "alice",
|
||||
mockTags: []string{"plan:enterprise-1"},
|
||||
wantName: "alice",
|
||||
},
|
||||
{
|
||||
name: "no account name",
|
||||
name: "no data",
|
||||
wantName: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := getSubscriptionAccountName(dotcom.ProductSubscriptionState{
|
||||
Account: &dotcom.ProductSubscriptionStateAccountUser{
|
||||
Username: test.mockUsername,
|
||||
},
|
||||
ActiveLicense: &dotcom.ProductSubscriptionStateActiveLicenseProductLicense{
|
||||
Info: &dotcom.ProductSubscriptionStateActiveLicenseProductLicenseInfo{
|
||||
Tags: test.mockTags,
|
||||
},
|
||||
},
|
||||
})
|
||||
got := getSubscriptionAccountName(test.mockTags)
|
||||
assert.Equal(t, test.wantName, got)
|
||||
})
|
||||
}
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
load("//dev:go_mockgen.bzl", "go_mockgen")
|
||||
|
||||
go_library(
|
||||
name = "productsubscriptiontest",
|
||||
srcs = ["mocks.go"],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor/productsubscription/productsubscriptiontest",
|
||||
visibility = ["//cmd/cody-gateway:__subpackages__"],
|
||||
deps = [
|
||||
"//cmd/cody-gateway/internal/actor/productsubscription",
|
||||
"//lib/enterpriseportal/codyaccess/v1:codyaccess",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_mockgen(
|
||||
name = "generate_mocks",
|
||||
out = "mocks.go",
|
||||
manifests = [
|
||||
"//:mockgen.yaml",
|
||||
"//:mockgen.test.yaml",
|
||||
"//:mockgen.temp.yaml",
|
||||
],
|
||||
deps = ["//cmd/cody-gateway/internal/actor/productsubscription"],
|
||||
)
|
||||
@ -0,0 +1,323 @@
|
||||
// Code generated by go-mockgen 1.3.7; DO NOT EDIT.
|
||||
//
|
||||
// This file was generated by running `sg generate` (or `go-mockgen`) at the root of
|
||||
// this repository. To add additional mocks to this or another package, add a new entry
|
||||
// to the mockgen.yaml file in the root of this repository.
|
||||
|
||||
package productsubscriptiontest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
productsubscription "github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor/productsubscription"
|
||||
v1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/codyaccess/v1"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// MockEnterprisePortalClient is a mock implementation of the
|
||||
// EnterprisePortalClient interface (from the package
|
||||
// github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor/productsubscription)
|
||||
// used for unit testing.
|
||||
type MockEnterprisePortalClient struct {
|
||||
// GetCodyGatewayAccessFunc is an instance of a mock function object
|
||||
// controlling the behavior of the method GetCodyGatewayAccess.
|
||||
GetCodyGatewayAccessFunc *EnterprisePortalClientGetCodyGatewayAccessFunc
|
||||
// ListCodyGatewayAccessesFunc is an instance of a mock function object
|
||||
// controlling the behavior of the method ListCodyGatewayAccesses.
|
||||
ListCodyGatewayAccessesFunc *EnterprisePortalClientListCodyGatewayAccessesFunc
|
||||
}
|
||||
|
||||
// NewMockEnterprisePortalClient creates a new mock of the
|
||||
// EnterprisePortalClient interface. All methods return zero values for all
|
||||
// results, unless overwritten.
|
||||
func NewMockEnterprisePortalClient() *MockEnterprisePortalClient {
|
||||
return &MockEnterprisePortalClient{
|
||||
GetCodyGatewayAccessFunc: &EnterprisePortalClientGetCodyGatewayAccessFunc{
|
||||
defaultHook: func(context.Context, *v1.GetCodyGatewayAccessRequest, ...grpc.CallOption) (r0 *v1.GetCodyGatewayAccessResponse, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
ListCodyGatewayAccessesFunc: &EnterprisePortalClientListCodyGatewayAccessesFunc{
|
||||
defaultHook: func(context.Context, *v1.ListCodyGatewayAccessesRequest, ...grpc.CallOption) (r0 *v1.ListCodyGatewayAccessesResponse, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewStrictMockEnterprisePortalClient creates a new mock of the
|
||||
// EnterprisePortalClient interface. All methods panic on invocation, unless
|
||||
// overwritten.
|
||||
func NewStrictMockEnterprisePortalClient() *MockEnterprisePortalClient {
|
||||
return &MockEnterprisePortalClient{
|
||||
GetCodyGatewayAccessFunc: &EnterprisePortalClientGetCodyGatewayAccessFunc{
|
||||
defaultHook: func(context.Context, *v1.GetCodyGatewayAccessRequest, ...grpc.CallOption) (*v1.GetCodyGatewayAccessResponse, error) {
|
||||
panic("unexpected invocation of MockEnterprisePortalClient.GetCodyGatewayAccess")
|
||||
},
|
||||
},
|
||||
ListCodyGatewayAccessesFunc: &EnterprisePortalClientListCodyGatewayAccessesFunc{
|
||||
defaultHook: func(context.Context, *v1.ListCodyGatewayAccessesRequest, ...grpc.CallOption) (*v1.ListCodyGatewayAccessesResponse, error) {
|
||||
panic("unexpected invocation of MockEnterprisePortalClient.ListCodyGatewayAccesses")
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewMockEnterprisePortalClientFrom creates a new mock of the
|
||||
// MockEnterprisePortalClient interface. All methods delegate to the given
|
||||
// implementation, unless overwritten.
|
||||
func NewMockEnterprisePortalClientFrom(i productsubscription.EnterprisePortalClient) *MockEnterprisePortalClient {
|
||||
return &MockEnterprisePortalClient{
|
||||
GetCodyGatewayAccessFunc: &EnterprisePortalClientGetCodyGatewayAccessFunc{
|
||||
defaultHook: i.GetCodyGatewayAccess,
|
||||
},
|
||||
ListCodyGatewayAccessesFunc: &EnterprisePortalClientListCodyGatewayAccessesFunc{
|
||||
defaultHook: i.ListCodyGatewayAccesses,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// EnterprisePortalClientGetCodyGatewayAccessFunc describes the behavior
|
||||
// when the GetCodyGatewayAccess method of the parent
|
||||
// MockEnterprisePortalClient instance is invoked.
|
||||
type EnterprisePortalClientGetCodyGatewayAccessFunc struct {
|
||||
defaultHook func(context.Context, *v1.GetCodyGatewayAccessRequest, ...grpc.CallOption) (*v1.GetCodyGatewayAccessResponse, error)
|
||||
hooks []func(context.Context, *v1.GetCodyGatewayAccessRequest, ...grpc.CallOption) (*v1.GetCodyGatewayAccessResponse, error)
|
||||
history []EnterprisePortalClientGetCodyGatewayAccessFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// GetCodyGatewayAccess delegates to the next hook function in the queue and
|
||||
// stores the parameter and result values of this invocation.
|
||||
func (m *MockEnterprisePortalClient) GetCodyGatewayAccess(v0 context.Context, v1 *v1.GetCodyGatewayAccessRequest, v2 ...grpc.CallOption) (*v1.GetCodyGatewayAccessResponse, error) {
|
||||
r0, r1 := m.GetCodyGatewayAccessFunc.nextHook()(v0, v1, v2...)
|
||||
m.GetCodyGatewayAccessFunc.appendCall(EnterprisePortalClientGetCodyGatewayAccessFuncCall{v0, v1, v2, r0, r1})
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SetDefaultHook sets function that is called when the GetCodyGatewayAccess
|
||||
// method of the parent MockEnterprisePortalClient instance is invoked and
|
||||
// the hook queue is empty.
|
||||
func (f *EnterprisePortalClientGetCodyGatewayAccessFunc) SetDefaultHook(hook func(context.Context, *v1.GetCodyGatewayAccessRequest, ...grpc.CallOption) (*v1.GetCodyGatewayAccessResponse, error)) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||
// GetCodyGatewayAccess method of the parent MockEnterprisePortalClient
|
||||
// instance invokes the hook at the front of the queue and discards it.
|
||||
// After the queue is empty, the default hook function is invoked for any
|
||||
// future action.
|
||||
func (f *EnterprisePortalClientGetCodyGatewayAccessFunc) PushHook(hook func(context.Context, *v1.GetCodyGatewayAccessRequest, ...grpc.CallOption) (*v1.GetCodyGatewayAccessResponse, error)) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *EnterprisePortalClientGetCodyGatewayAccessFunc) SetDefaultReturn(r0 *v1.GetCodyGatewayAccessResponse, r1 error) {
|
||||
f.SetDefaultHook(func(context.Context, *v1.GetCodyGatewayAccessRequest, ...grpc.CallOption) (*v1.GetCodyGatewayAccessResponse, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *EnterprisePortalClientGetCodyGatewayAccessFunc) PushReturn(r0 *v1.GetCodyGatewayAccessResponse, r1 error) {
|
||||
f.PushHook(func(context.Context, *v1.GetCodyGatewayAccessRequest, ...grpc.CallOption) (*v1.GetCodyGatewayAccessResponse, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
func (f *EnterprisePortalClientGetCodyGatewayAccessFunc) nextHook() func(context.Context, *v1.GetCodyGatewayAccessRequest, ...grpc.CallOption) (*v1.GetCodyGatewayAccessResponse, error) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
if len(f.hooks) == 0 {
|
||||
return f.defaultHook
|
||||
}
|
||||
|
||||
hook := f.hooks[0]
|
||||
f.hooks = f.hooks[1:]
|
||||
return hook
|
||||
}
|
||||
|
||||
func (f *EnterprisePortalClientGetCodyGatewayAccessFunc) appendCall(r0 EnterprisePortalClientGetCodyGatewayAccessFuncCall) {
|
||||
f.mutex.Lock()
|
||||
f.history = append(f.history, r0)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// History returns a sequence of
|
||||
// EnterprisePortalClientGetCodyGatewayAccessFuncCall objects describing the
|
||||
// invocations of this function.
|
||||
func (f *EnterprisePortalClientGetCodyGatewayAccessFunc) History() []EnterprisePortalClientGetCodyGatewayAccessFuncCall {
|
||||
f.mutex.Lock()
|
||||
history := make([]EnterprisePortalClientGetCodyGatewayAccessFuncCall, len(f.history))
|
||||
copy(history, f.history)
|
||||
f.mutex.Unlock()
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// EnterprisePortalClientGetCodyGatewayAccessFuncCall is an object that
|
||||
// describes an invocation of method GetCodyGatewayAccess on an instance of
|
||||
// MockEnterprisePortalClient.
|
||||
type EnterprisePortalClientGetCodyGatewayAccessFuncCall struct {
|
||||
// Arg0 is the value of the 1st argument passed to this method
|
||||
// invocation.
|
||||
Arg0 context.Context
|
||||
// Arg1 is the value of the 2nd argument passed to this method
|
||||
// invocation.
|
||||
Arg1 *v1.GetCodyGatewayAccessRequest
|
||||
// Arg2 is a slice containing the values of the variadic arguments
|
||||
// passed to this method invocation.
|
||||
Arg2 []grpc.CallOption
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 *v1.GetCodyGatewayAccessResponse
|
||||
// Result1 is the value of the 2nd result returned from this method
|
||||
// invocation.
|
||||
Result1 error
|
||||
}
|
||||
|
||||
// Args returns an interface slice containing the arguments of this
|
||||
// invocation. The variadic slice argument is flattened in this array such
|
||||
// that one positional argument and three variadic arguments would result in
|
||||
// a slice of four, not two.
|
||||
func (c EnterprisePortalClientGetCodyGatewayAccessFuncCall) Args() []interface{} {
|
||||
trailing := []interface{}{}
|
||||
for _, val := range c.Arg2 {
|
||||
trailing = append(trailing, val)
|
||||
}
|
||||
|
||||
return append([]interface{}{c.Arg0, c.Arg1}, trailing...)
|
||||
}
|
||||
|
||||
// Results returns an interface slice containing the results of this
|
||||
// invocation.
|
||||
func (c EnterprisePortalClientGetCodyGatewayAccessFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0, c.Result1}
|
||||
}
|
||||
|
||||
// EnterprisePortalClientListCodyGatewayAccessesFunc describes the behavior
|
||||
// when the ListCodyGatewayAccesses method of the parent
|
||||
// MockEnterprisePortalClient instance is invoked.
|
||||
type EnterprisePortalClientListCodyGatewayAccessesFunc struct {
|
||||
defaultHook func(context.Context, *v1.ListCodyGatewayAccessesRequest, ...grpc.CallOption) (*v1.ListCodyGatewayAccessesResponse, error)
|
||||
hooks []func(context.Context, *v1.ListCodyGatewayAccessesRequest, ...grpc.CallOption) (*v1.ListCodyGatewayAccessesResponse, error)
|
||||
history []EnterprisePortalClientListCodyGatewayAccessesFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// ListCodyGatewayAccesses delegates to the next hook function in the queue
|
||||
// and stores the parameter and result values of this invocation.
|
||||
func (m *MockEnterprisePortalClient) ListCodyGatewayAccesses(v0 context.Context, v1 *v1.ListCodyGatewayAccessesRequest, v2 ...grpc.CallOption) (*v1.ListCodyGatewayAccessesResponse, error) {
|
||||
r0, r1 := m.ListCodyGatewayAccessesFunc.nextHook()(v0, v1, v2...)
|
||||
m.ListCodyGatewayAccessesFunc.appendCall(EnterprisePortalClientListCodyGatewayAccessesFuncCall{v0, v1, v2, r0, r1})
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SetDefaultHook sets function that is called when the
|
||||
// ListCodyGatewayAccesses method of the parent MockEnterprisePortalClient
|
||||
// instance is invoked and the hook queue is empty.
|
||||
func (f *EnterprisePortalClientListCodyGatewayAccessesFunc) SetDefaultHook(hook func(context.Context, *v1.ListCodyGatewayAccessesRequest, ...grpc.CallOption) (*v1.ListCodyGatewayAccessesResponse, error)) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||
// ListCodyGatewayAccesses method of the parent MockEnterprisePortalClient
|
||||
// instance invokes the hook at the front of the queue and discards it.
|
||||
// After the queue is empty, the default hook function is invoked for any
|
||||
// future action.
|
||||
func (f *EnterprisePortalClientListCodyGatewayAccessesFunc) PushHook(hook func(context.Context, *v1.ListCodyGatewayAccessesRequest, ...grpc.CallOption) (*v1.ListCodyGatewayAccessesResponse, error)) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *EnterprisePortalClientListCodyGatewayAccessesFunc) SetDefaultReturn(r0 *v1.ListCodyGatewayAccessesResponse, r1 error) {
|
||||
f.SetDefaultHook(func(context.Context, *v1.ListCodyGatewayAccessesRequest, ...grpc.CallOption) (*v1.ListCodyGatewayAccessesResponse, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *EnterprisePortalClientListCodyGatewayAccessesFunc) PushReturn(r0 *v1.ListCodyGatewayAccessesResponse, r1 error) {
|
||||
f.PushHook(func(context.Context, *v1.ListCodyGatewayAccessesRequest, ...grpc.CallOption) (*v1.ListCodyGatewayAccessesResponse, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
func (f *EnterprisePortalClientListCodyGatewayAccessesFunc) nextHook() func(context.Context, *v1.ListCodyGatewayAccessesRequest, ...grpc.CallOption) (*v1.ListCodyGatewayAccessesResponse, error) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
if len(f.hooks) == 0 {
|
||||
return f.defaultHook
|
||||
}
|
||||
|
||||
hook := f.hooks[0]
|
||||
f.hooks = f.hooks[1:]
|
||||
return hook
|
||||
}
|
||||
|
||||
func (f *EnterprisePortalClientListCodyGatewayAccessesFunc) appendCall(r0 EnterprisePortalClientListCodyGatewayAccessesFuncCall) {
|
||||
f.mutex.Lock()
|
||||
f.history = append(f.history, r0)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// History returns a sequence of
|
||||
// EnterprisePortalClientListCodyGatewayAccessesFuncCall objects describing
|
||||
// the invocations of this function.
|
||||
func (f *EnterprisePortalClientListCodyGatewayAccessesFunc) History() []EnterprisePortalClientListCodyGatewayAccessesFuncCall {
|
||||
f.mutex.Lock()
|
||||
history := make([]EnterprisePortalClientListCodyGatewayAccessesFuncCall, len(f.history))
|
||||
copy(history, f.history)
|
||||
f.mutex.Unlock()
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// EnterprisePortalClientListCodyGatewayAccessesFuncCall is an object that
|
||||
// describes an invocation of method ListCodyGatewayAccesses on an instance
|
||||
// of MockEnterprisePortalClient.
|
||||
type EnterprisePortalClientListCodyGatewayAccessesFuncCall struct {
|
||||
// Arg0 is the value of the 1st argument passed to this method
|
||||
// invocation.
|
||||
Arg0 context.Context
|
||||
// Arg1 is the value of the 2nd argument passed to this method
|
||||
// invocation.
|
||||
Arg1 *v1.ListCodyGatewayAccessesRequest
|
||||
// Arg2 is a slice containing the values of the variadic arguments
|
||||
// passed to this method invocation.
|
||||
Arg2 []grpc.CallOption
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 *v1.ListCodyGatewayAccessesResponse
|
||||
// Result1 is the value of the 2nd result returned from this method
|
||||
// invocation.
|
||||
Result1 error
|
||||
}
|
||||
|
||||
// Args returns an interface slice containing the arguments of this
|
||||
// invocation. The variadic slice argument is flattened in this array such
|
||||
// that one positional argument and three variadic arguments would result in
|
||||
// a slice of four, not two.
|
||||
func (c EnterprisePortalClientListCodyGatewayAccessesFuncCall) Args() []interface{} {
|
||||
trailing := []interface{}{}
|
||||
for _, val := range c.Arg2 {
|
||||
trailing = append(trailing, val)
|
||||
}
|
||||
|
||||
return append([]interface{}{c.Arg0, c.Arg1}, trailing...)
|
||||
}
|
||||
|
||||
// Results returns an interface slice containing the results of this
|
||||
// invocation.
|
||||
func (c EnterprisePortalClientListCodyGatewayAccessesFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0, c.Result1}
|
||||
}
|
||||
@ -149,7 +149,11 @@ func (s *Sources) SyncAll(ctx context.Context, logger log.Logger) error {
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to sync %s", src.Name())
|
||||
}
|
||||
syncLogger.Info("Completed sync", log.Duration("sync_duration", time.Since(start)), log.Int("seen", seen))
|
||||
span.SetAttributes(
|
||||
attribute.Int("seen_actors", seen))
|
||||
syncLogger.Info("Completed sync",
|
||||
log.Duration("sync_duration", time.Since(start)),
|
||||
log.Int("seen", seen))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@ -32,18 +32,18 @@ go_test(
|
||||
"//cmd/cody-gateway/internal/actor",
|
||||
"//cmd/cody-gateway/internal/actor/anonymous",
|
||||
"//cmd/cody-gateway/internal/actor/productsubscription",
|
||||
"//cmd/cody-gateway/internal/dotcom",
|
||||
"//cmd/cody-gateway/internal/actor/productsubscription/productsubscriptiontest",
|
||||
"//cmd/cody-gateway/internal/events",
|
||||
"//internal/codygateway",
|
||||
"//internal/licensing",
|
||||
"//internal/productsubscription",
|
||||
"//lib/enterpriseportal/codyaccess/v1:codyaccess",
|
||||
"//lib/errors",
|
||||
"@com_github_derision_test_go_mockgen_v2//testutil/require",
|
||||
"@com_github_khan_genqlient//graphql",
|
||||
"@com_github_sourcegraph_log//logtest",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
"@com_github_vektah_gqlparser_v2//gqlerror",
|
||||
"@org_golang_google_grpc//codes",
|
||||
"@org_golang_google_grpc//status",
|
||||
"@org_golang_google_protobuf//types/known/durationpb",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@ -2,28 +2,27 @@ package auth
|
||||
|
||||
// pre-commit:ignore_sourcegraph_token
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
mockrequire "github.com/derision-test/go-mockgen/v2/testutil/require"
|
||||
"github.com/sourcegraph/log/logtest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor/anonymous"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor/productsubscription"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/dotcom"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor/productsubscription/productsubscriptiontest"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/events"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codygateway"
|
||||
"github.com/sourcegraph/sourcegraph/internal/licensing"
|
||||
internalproductsubscription "github.com/sourcegraph/sourcegraph/internal/productsubscription"
|
||||
codyaccessv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/codyaccess/v1"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
@ -58,36 +57,25 @@ func TestAuthenticatorMiddleware(t *testing.T) {
|
||||
|
||||
t.Run("authenticated without cache hit", func(t *testing.T) {
|
||||
cache := NewMockListingCache()
|
||||
client := dotcom.NewMockClient()
|
||||
client.MakeRequestFunc.SetDefaultHook(func(_ context.Context, _ *graphql.Request, resp *graphql.Response) error {
|
||||
resp.Data.(*dotcom.CheckAccessTokenResponse).Dotcom = dotcom.CheckAccessTokenDotcomDotcomQuery{
|
||||
ProductSubscriptionByAccessToken: dotcom.CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription{
|
||||
ProductSubscriptionState: dotcom.ProductSubscriptionState{
|
||||
Id: "UHJvZHVjdFN1YnNjcmlwdGlvbjoiNjQ1MmE4ZmMtZTY1MC00NWE3LWEwYTItMzU3Zjc3NmIzYjQ2Ig==",
|
||||
Uuid: "6452a8fc-e650-45a7-a0a2-357f776b3b46",
|
||||
IsArchived: false,
|
||||
CodyGatewayAccess: dotcom.ProductSubscriptionStateCodyGatewayAccess{
|
||||
CodyGatewayAccessFields: dotcom.CodyGatewayAccessFields{
|
||||
Enabled: true,
|
||||
ChatCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsChatCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
CodeCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsCodeCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
client := productsubscriptiontest.NewMockEnterprisePortalClient()
|
||||
client.GetCodyGatewayAccessFunc.PushReturn(
|
||||
&codyaccessv1.GetCodyGatewayAccessResponse{
|
||||
Access: &codyaccessv1.CodyGatewayAccess{
|
||||
SubscriptionId: "es_6452a8fc-e650-45a7-a0a2-357f776b3b46",
|
||||
Enabled: true,
|
||||
ChatCompletionsRateLimit: &codyaccessv1.CodyGatewayRateLimit{
|
||||
Limit: 10,
|
||||
IntervalDuration: durationpb.New(10 * time.Second),
|
||||
},
|
||||
CodeCompletionsRateLimit: &codyaccessv1.CodyGatewayRateLimit{
|
||||
Limit: 10,
|
||||
IntervalDuration: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.NotNil(t, actor.FromContext(r.Context()))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@ -99,10 +87,10 @@ func TestAuthenticatorMiddleware(t *testing.T) {
|
||||
(&Authenticator{
|
||||
Logger: logger,
|
||||
EventLogger: events.NewStdoutLogger(logger),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, false, concurrencyConfig)),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, concurrencyConfig)),
|
||||
}).Middleware(next).ServeHTTP(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
mockrequire.Called(t, client.MakeRequestFunc)
|
||||
mockrequire.Called(t, client.GetCodyGatewayAccessFunc)
|
||||
})
|
||||
|
||||
t.Run("authenticated with cache hit", func(t *testing.T) {
|
||||
@ -111,7 +99,7 @@ func TestAuthenticatorMiddleware(t *testing.T) {
|
||||
[]byte(`{"id":"UHJvZHVjdFN1YnNjcmlwdGlvbjoiNjQ1MmE4ZmMtZTY1MC00NWE3LWEwYTItMzU3Zjc3NmIzYjQ2Ig==","accessEnabled":true,"rateLimit":null}`),
|
||||
true,
|
||||
)
|
||||
client := dotcom.NewMockClient()
|
||||
client := productsubscriptiontest.NewMockEnterprisePortalClient()
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.NotNil(t, actor.FromContext(r.Context()))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@ -123,10 +111,10 @@ func TestAuthenticatorMiddleware(t *testing.T) {
|
||||
(&Authenticator{
|
||||
Logger: logger,
|
||||
EventLogger: events.NewStdoutLogger(logger),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, false, concurrencyConfig)),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, concurrencyConfig)),
|
||||
}).Middleware(next).ServeHTTP(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
mockrequire.NotCalled(t, client.MakeRequestFunc)
|
||||
mockrequire.NotCalled(t, client.GetCodyGatewayAccessFunc)
|
||||
})
|
||||
|
||||
t.Run("authenticated but not enabled", func(t *testing.T) {
|
||||
@ -135,7 +123,7 @@ func TestAuthenticatorMiddleware(t *testing.T) {
|
||||
[]byte(`{"id":"UHJvZHVjdFN1YnNjcmlwdGlvbjoiNjQ1MmE4ZmMtZTY1MC00NWE3LWEwYTItMzU3Zjc3NmIzYjQ2Ig==","accessEnabled":false,"rateLimit":null}`),
|
||||
true,
|
||||
)
|
||||
client := dotcom.NewMockClient()
|
||||
client := productsubscriptiontest.NewMockEnterprisePortalClient()
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{}`))
|
||||
@ -143,7 +131,7 @@ func TestAuthenticatorMiddleware(t *testing.T) {
|
||||
(&Authenticator{
|
||||
Logger: logger,
|
||||
EventLogger: events.NewStdoutLogger(logger),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, false, concurrencyConfig)),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, concurrencyConfig)),
|
||||
}).Middleware(next).ServeHTTP(w, r)
|
||||
assert.Equal(t, http.StatusForbidden, w.Code)
|
||||
})
|
||||
@ -154,7 +142,7 @@ func TestAuthenticatorMiddleware(t *testing.T) {
|
||||
[]byte(`{"id":"UHJvZHVjdFN1YnNjcmlwdGlvbjoiNjQ1MmE4ZmMtZTY1MC00NWE3LWEwYTItMzU3Zjc3NmIzYjQ2Ig==","accessEnabled":false,"endpointAccess":{"/v1/attribution":true},"rateLimit":null}`),
|
||||
true,
|
||||
)
|
||||
client := dotcom.NewMockClient()
|
||||
client := productsubscriptiontest.NewMockEnterprisePortalClient()
|
||||
|
||||
t.Run("bypass works for attribution", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
@ -163,7 +151,7 @@ func TestAuthenticatorMiddleware(t *testing.T) {
|
||||
(&Authenticator{
|
||||
Logger: logger,
|
||||
EventLogger: events.NewStdoutLogger(logger),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, false, concurrencyConfig)),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, concurrencyConfig)),
|
||||
}).Middleware(next).ServeHTTP(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
})
|
||||
@ -175,7 +163,7 @@ func TestAuthenticatorMiddleware(t *testing.T) {
|
||||
(&Authenticator{
|
||||
Logger: logger,
|
||||
EventLogger: events.NewStdoutLogger(logger),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, false, concurrencyConfig)),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, concurrencyConfig)),
|
||||
}).Middleware(next).ServeHTTP(w, r)
|
||||
assert.Equal(t, http.StatusForbidden, w.Code)
|
||||
})
|
||||
@ -183,15 +171,11 @@ func TestAuthenticatorMiddleware(t *testing.T) {
|
||||
|
||||
t.Run("access token denied from sources", func(t *testing.T) {
|
||||
cache := NewMockListingCache()
|
||||
client := dotcom.NewMockClient()
|
||||
client.MakeRequestFunc.SetDefaultHook(func(_ context.Context, _ *graphql.Request, resp *graphql.Response) error {
|
||||
return gqlerror.List{
|
||||
{
|
||||
Message: "access denied",
|
||||
Extensions: map[string]any{"code": internalproductsubscription.GQLErrCodeProductSubscriptionNotFound},
|
||||
},
|
||||
}
|
||||
})
|
||||
client := productsubscriptiontest.NewMockEnterprisePortalClient()
|
||||
client.GetCodyGatewayAccessFunc.PushReturn(
|
||||
nil,
|
||||
status.Error(codes.NotFound, "not found"),
|
||||
)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{}`))
|
||||
@ -199,17 +183,19 @@ func TestAuthenticatorMiddleware(t *testing.T) {
|
||||
(&Authenticator{
|
||||
Logger: logger,
|
||||
EventLogger: events.NewStdoutLogger(logger),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, true, concurrencyConfig)),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, concurrencyConfig)),
|
||||
}).Middleware(next).ServeHTTP(w, r)
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
mockrequire.Called(t, client.GetCodyGatewayAccessFunc)
|
||||
})
|
||||
|
||||
t.Run("server error from sources", func(t *testing.T) {
|
||||
cache := NewMockListingCache()
|
||||
client := dotcom.NewMockClient()
|
||||
client.MakeRequestFunc.SetDefaultHook(func(_ context.Context, _ *graphql.Request, resp *graphql.Response) error {
|
||||
return errors.New("server error")
|
||||
})
|
||||
client := productsubscriptiontest.NewMockEnterprisePortalClient()
|
||||
client.GetCodyGatewayAccessFunc.SetDefaultReturn(
|
||||
nil,
|
||||
errors.New("server error"),
|
||||
)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{}`))
|
||||
@ -217,155 +203,8 @@ func TestAuthenticatorMiddleware(t *testing.T) {
|
||||
(&Authenticator{
|
||||
Logger: logger,
|
||||
EventLogger: events.NewStdoutLogger(logger),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, true, concurrencyConfig)),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, concurrencyConfig)),
|
||||
}).Middleware(next).ServeHTTP(w, r)
|
||||
assert.Equal(t, http.StatusServiceUnavailable, w.Code)
|
||||
})
|
||||
|
||||
t.Run("internal mode, authenticated but not dev license", func(t *testing.T) {
|
||||
cache := NewMockListingCache()
|
||||
client := dotcom.NewMockClient()
|
||||
client.MakeRequestFunc.SetDefaultHook(func(_ context.Context, _ *graphql.Request, resp *graphql.Response) error {
|
||||
resp.Data.(*dotcom.CheckAccessTokenResponse).Dotcom = dotcom.CheckAccessTokenDotcomDotcomQuery{
|
||||
ProductSubscriptionByAccessToken: dotcom.CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription{
|
||||
ProductSubscriptionState: dotcom.ProductSubscriptionState{
|
||||
Id: "UHJvZHVjdFN1YnNjcmlwdGlvbjoiNjQ1MmE4ZmMtZTY1MC00NWE3LWEwYTItMzU3Zjc3NmIzYjQ2Ig==",
|
||||
Uuid: "6452a8fc-e650-45a7-a0a2-357f776b3b46",
|
||||
IsArchived: false,
|
||||
CodyGatewayAccess: dotcom.ProductSubscriptionStateCodyGatewayAccess{
|
||||
CodyGatewayAccessFields: dotcom.CodyGatewayAccessFields{
|
||||
Enabled: true,
|
||||
ChatCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsChatCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
CodeCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsCodeCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ActiveLicense: &dotcom.ProductSubscriptionStateActiveLicenseProductLicense{
|
||||
Info: &dotcom.ProductSubscriptionStateActiveLicenseProductLicenseInfo{
|
||||
Tags: []string{""},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{}`))
|
||||
r.Header.Set("Authorization", "Bearer sgs_abc1228e23e789431f08cd15e9be20e69b8694c2dff701b81d16250a4a861f37")
|
||||
(&Authenticator{
|
||||
Logger: logger,
|
||||
EventLogger: events.NewStdoutLogger(logger),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, true, concurrencyConfig)),
|
||||
}).Middleware(next).ServeHTTP(w, r)
|
||||
assert.Equal(t, http.StatusForbidden, w.Code)
|
||||
})
|
||||
|
||||
t.Run("internal mode, authenticated dev license", func(t *testing.T) {
|
||||
cache := NewMockListingCache()
|
||||
client := dotcom.NewMockClient()
|
||||
client.MakeRequestFunc.SetDefaultHook(func(_ context.Context, _ *graphql.Request, resp *graphql.Response) error {
|
||||
resp.Data.(*dotcom.CheckAccessTokenResponse).Dotcom = dotcom.CheckAccessTokenDotcomDotcomQuery{
|
||||
ProductSubscriptionByAccessToken: dotcom.CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription{
|
||||
ProductSubscriptionState: dotcom.ProductSubscriptionState{
|
||||
Id: "UHJvZHVjdFN1YnNjcmlwdGlvbjoiNjQ1MmE4ZmMtZTY1MC00NWE3LWEwYTItMzU3Zjc3NmIzYjQ2Ig==",
|
||||
Uuid: "6452a8fc-e650-45a7-a0a2-357f776b3b46",
|
||||
IsArchived: false,
|
||||
CodyGatewayAccess: dotcom.ProductSubscriptionStateCodyGatewayAccess{
|
||||
CodyGatewayAccessFields: dotcom.CodyGatewayAccessFields{
|
||||
Enabled: true,
|
||||
ChatCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsChatCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
CodeCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsCodeCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ActiveLicense: &dotcom.ProductSubscriptionStateActiveLicenseProductLicense{
|
||||
Info: &dotcom.ProductSubscriptionStateActiveLicenseProductLicenseInfo{
|
||||
Tags: []string{licensing.DevTag},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{}`))
|
||||
r.Header.Set("Authorization", "Bearer sgs_abc1228e23e789431f08cd15e9be20e69b8694c2dff701b81d16250a4a861f37")
|
||||
(&Authenticator{
|
||||
Logger: logger,
|
||||
EventLogger: events.NewStdoutLogger(logger),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, true, concurrencyConfig)),
|
||||
}).Middleware(next).ServeHTTP(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
})
|
||||
|
||||
t.Run("internal mode, authenticated internal license", func(t *testing.T) {
|
||||
cache := NewMockListingCache()
|
||||
client := dotcom.NewMockClient()
|
||||
client.MakeRequestFunc.SetDefaultHook(func(_ context.Context, _ *graphql.Request, resp *graphql.Response) error {
|
||||
resp.Data.(*dotcom.CheckAccessTokenResponse).Dotcom = dotcom.CheckAccessTokenDotcomDotcomQuery{
|
||||
ProductSubscriptionByAccessToken: dotcom.CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription{
|
||||
ProductSubscriptionState: dotcom.ProductSubscriptionState{
|
||||
Id: "UHJvZHVjdFN1YnNjcmlwdGlvbjoiNjQ1MmE4ZmMtZTY1MC00NWE3LWEwYTItMzU3Zjc3NmIzYjQ2Ig==",
|
||||
Uuid: "6452a8fc-e650-45a7-a0a2-357f776b3b46",
|
||||
IsArchived: false,
|
||||
CodyGatewayAccess: dotcom.ProductSubscriptionStateCodyGatewayAccess{
|
||||
CodyGatewayAccessFields: dotcom.CodyGatewayAccessFields{
|
||||
Enabled: true,
|
||||
ChatCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsChatCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
CodeCompletionsRateLimit: &dotcom.CodyGatewayAccessFieldsCodeCompletionsRateLimitCodyGatewayRateLimit{
|
||||
RateLimitFields: dotcom.RateLimitFields{
|
||||
Limit: 10,
|
||||
IntervalSeconds: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ActiveLicense: &dotcom.ProductSubscriptionStateActiveLicenseProductLicense{
|
||||
Info: &dotcom.ProductSubscriptionStateActiveLicenseProductLicenseInfo{
|
||||
Tags: []string{licensing.DevTag},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{}`))
|
||||
r.Header.Set("Authorization", "Bearer sgs_abc1228e23e789431f08cd15e9be20e69b8694c2dff701b81d16250a4a861f37")
|
||||
(&Authenticator{
|
||||
Logger: logger,
|
||||
EventLogger: events.NewStdoutLogger(logger),
|
||||
Sources: actor.NewSources(productsubscription.NewSource(logger, cache, client, true, concurrencyConfig)),
|
||||
}).Middleware(next).ServeHTTP(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
})
|
||||
}
|
||||
|
||||
@ -13,13 +13,13 @@ func TestOpInQuery(t *testing.T) {
|
||||
var requestReceived bool
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requestReceived = true
|
||||
assert.Equal(t, r.URL.RawQuery, "CheckAccessToken")
|
||||
assert.Equal(t, r.URL.RawQuery, "CheckDotcomUserAccessToken")
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := NewClient(srv.URL, "test-token", "random", "dev")
|
||||
// We don't care about the actual result of the call
|
||||
_, _ = CheckAccessToken(context.Background(), c, "slk_foobar")
|
||||
_, _ = CheckDotcomUserAccessToken(context.Background(), c, "slk_foobar")
|
||||
// But we do care that we did get through to the handler
|
||||
assert.True(t, requestReceived)
|
||||
}
|
||||
|
||||
@ -10,135 +10,6 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/dotcom/genhelper"
|
||||
)
|
||||
|
||||
// CheckAccessTokenDotcomDotcomQuery includes the requested fields of the GraphQL type DotcomQuery.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// Mutations that are only used on Sourcegraph.com.
|
||||
// FOR INTERNAL USE ONLY.
|
||||
type CheckAccessTokenDotcomDotcomQuery struct {
|
||||
// The access available to the product subscription with the given access token.
|
||||
// The returned ProductSubscription may be archived or not associated with an active license.
|
||||
//
|
||||
// Only Sourcegraph.com site admins, the account owners of the product subscription, and
|
||||
// specific service accounts may perform this query.
|
||||
// FOR INTERNAL USE ONLY.
|
||||
ProductSubscriptionByAccessToken CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription `json:"productSubscriptionByAccessToken"`
|
||||
}
|
||||
|
||||
// GetProductSubscriptionByAccessToken returns CheckAccessTokenDotcomDotcomQuery.ProductSubscriptionByAccessToken, and is useful for accessing the field via an interface.
|
||||
func (v *CheckAccessTokenDotcomDotcomQuery) GetProductSubscriptionByAccessToken() CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription {
|
||||
return v.ProductSubscriptionByAccessToken
|
||||
}
|
||||
|
||||
// CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription includes the requested fields of the GraphQL type ProductSubscription.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// A product subscription that was created on Sourcegraph.com.
|
||||
// FOR INTERNAL USE ONLY.
|
||||
type CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription struct {
|
||||
ProductSubscriptionState `json:"-"`
|
||||
}
|
||||
|
||||
// GetId returns CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription.Id, and is useful for accessing the field via an interface.
|
||||
func (v *CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription) GetId() string {
|
||||
return v.ProductSubscriptionState.Id
|
||||
}
|
||||
|
||||
// GetUuid returns CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription.Uuid, and is useful for accessing the field via an interface.
|
||||
func (v *CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription) GetUuid() string {
|
||||
return v.ProductSubscriptionState.Uuid
|
||||
}
|
||||
|
||||
// GetAccount returns CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription.Account, and is useful for accessing the field via an interface.
|
||||
func (v *CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription) GetAccount() *ProductSubscriptionStateAccountUser {
|
||||
return v.ProductSubscriptionState.Account
|
||||
}
|
||||
|
||||
// GetIsArchived returns CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription.IsArchived, and is useful for accessing the field via an interface.
|
||||
func (v *CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription) GetIsArchived() bool {
|
||||
return v.ProductSubscriptionState.IsArchived
|
||||
}
|
||||
|
||||
// GetCodyGatewayAccess returns CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription.CodyGatewayAccess, and is useful for accessing the field via an interface.
|
||||
func (v *CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription) GetCodyGatewayAccess() ProductSubscriptionStateCodyGatewayAccess {
|
||||
return v.ProductSubscriptionState.CodyGatewayAccess
|
||||
}
|
||||
|
||||
// GetActiveLicense returns CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription.ActiveLicense, and is useful for accessing the field via an interface.
|
||||
func (v *CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription) GetActiveLicense() *ProductSubscriptionStateActiveLicenseProductLicense {
|
||||
return v.ProductSubscriptionState.ActiveLicense
|
||||
}
|
||||
|
||||
func (v *CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription) UnmarshalJSON(b []byte) error {
|
||||
|
||||
if string(b) == "null" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var firstPass struct {
|
||||
*CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription
|
||||
graphql.NoUnmarshalJSON
|
||||
}
|
||||
firstPass.CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription = v
|
||||
|
||||
err := json.Unmarshal(b, &firstPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(
|
||||
b, &v.ProductSubscriptionState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type __premarshalCheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription struct {
|
||||
Id string `json:"id"`
|
||||
|
||||
Uuid string `json:"uuid"`
|
||||
|
||||
Account *ProductSubscriptionStateAccountUser `json:"account"`
|
||||
|
||||
IsArchived bool `json:"isArchived"`
|
||||
|
||||
CodyGatewayAccess ProductSubscriptionStateCodyGatewayAccess `json:"codyGatewayAccess"`
|
||||
|
||||
ActiveLicense *ProductSubscriptionStateActiveLicenseProductLicense `json:"activeLicense"`
|
||||
}
|
||||
|
||||
func (v *CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription) MarshalJSON() ([]byte, error) {
|
||||
premarshaled, err := v.__premarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(premarshaled)
|
||||
}
|
||||
|
||||
func (v *CheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription) __premarshalJSON() (*__premarshalCheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription, error) {
|
||||
var retval __premarshalCheckAccessTokenDotcomDotcomQueryProductSubscriptionByAccessTokenProductSubscription
|
||||
|
||||
retval.Id = v.ProductSubscriptionState.Id
|
||||
retval.Uuid = v.ProductSubscriptionState.Uuid
|
||||
retval.Account = v.ProductSubscriptionState.Account
|
||||
retval.IsArchived = v.ProductSubscriptionState.IsArchived
|
||||
retval.CodyGatewayAccess = v.ProductSubscriptionState.CodyGatewayAccess
|
||||
retval.ActiveLicense = v.ProductSubscriptionState.ActiveLicense
|
||||
return &retval, nil
|
||||
}
|
||||
|
||||
// CheckAccessTokenResponse is returned by CheckAccessToken on success.
|
||||
type CheckAccessTokenResponse struct {
|
||||
// Queries that are only used on Sourcegraph.com.
|
||||
//
|
||||
// FOR INTERNAL USE ONLY.
|
||||
Dotcom CheckAccessTokenDotcomDotcomQuery `json:"dotcom"`
|
||||
}
|
||||
|
||||
// GetDotcom returns CheckAccessTokenResponse.Dotcom, and is useful for accessing the field via an interface.
|
||||
func (v *CheckAccessTokenResponse) GetDotcom() CheckAccessTokenDotcomDotcomQuery { return v.Dotcom }
|
||||
|
||||
// CheckDotcomUserAccessTokenDotcomDotcomQuery includes the requested fields of the GraphQL type DotcomQuery.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
@ -642,463 +513,6 @@ func (v *DotcomUserStateCodyGatewayAccess) __premarshalJSON() (*__premarshalDotc
|
||||
return &retval, nil
|
||||
}
|
||||
|
||||
// ListProductSubscriptionFields includes the GraphQL fields of ProductSubscription requested by the fragment ListProductSubscriptionFields.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// A product subscription that was created on Sourcegraph.com.
|
||||
// FOR INTERNAL USE ONLY.
|
||||
type ListProductSubscriptionFields struct {
|
||||
ProductSubscriptionState `json:"-"`
|
||||
// Available access tokens for authenticating as the subscription holder with managed
|
||||
// Sourcegraph services.
|
||||
SourcegraphAccessTokens []string `json:"sourcegraphAccessTokens"`
|
||||
}
|
||||
|
||||
// GetSourcegraphAccessTokens returns ListProductSubscriptionFields.SourcegraphAccessTokens, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionFields) GetSourcegraphAccessTokens() []string {
|
||||
return v.SourcegraphAccessTokens
|
||||
}
|
||||
|
||||
// GetId returns ListProductSubscriptionFields.Id, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionFields) GetId() string { return v.ProductSubscriptionState.Id }
|
||||
|
||||
// GetUuid returns ListProductSubscriptionFields.Uuid, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionFields) GetUuid() string { return v.ProductSubscriptionState.Uuid }
|
||||
|
||||
// GetAccount returns ListProductSubscriptionFields.Account, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionFields) GetAccount() *ProductSubscriptionStateAccountUser {
|
||||
return v.ProductSubscriptionState.Account
|
||||
}
|
||||
|
||||
// GetIsArchived returns ListProductSubscriptionFields.IsArchived, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionFields) GetIsArchived() bool {
|
||||
return v.ProductSubscriptionState.IsArchived
|
||||
}
|
||||
|
||||
// GetCodyGatewayAccess returns ListProductSubscriptionFields.CodyGatewayAccess, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionFields) GetCodyGatewayAccess() ProductSubscriptionStateCodyGatewayAccess {
|
||||
return v.ProductSubscriptionState.CodyGatewayAccess
|
||||
}
|
||||
|
||||
// GetActiveLicense returns ListProductSubscriptionFields.ActiveLicense, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionFields) GetActiveLicense() *ProductSubscriptionStateActiveLicenseProductLicense {
|
||||
return v.ProductSubscriptionState.ActiveLicense
|
||||
}
|
||||
|
||||
func (v *ListProductSubscriptionFields) UnmarshalJSON(b []byte) error {
|
||||
|
||||
if string(b) == "null" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var firstPass struct {
|
||||
*ListProductSubscriptionFields
|
||||
graphql.NoUnmarshalJSON
|
||||
}
|
||||
firstPass.ListProductSubscriptionFields = v
|
||||
|
||||
err := json.Unmarshal(b, &firstPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(
|
||||
b, &v.ProductSubscriptionState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type __premarshalListProductSubscriptionFields struct {
|
||||
SourcegraphAccessTokens []string `json:"sourcegraphAccessTokens"`
|
||||
|
||||
Id string `json:"id"`
|
||||
|
||||
Uuid string `json:"uuid"`
|
||||
|
||||
Account *ProductSubscriptionStateAccountUser `json:"account"`
|
||||
|
||||
IsArchived bool `json:"isArchived"`
|
||||
|
||||
CodyGatewayAccess ProductSubscriptionStateCodyGatewayAccess `json:"codyGatewayAccess"`
|
||||
|
||||
ActiveLicense *ProductSubscriptionStateActiveLicenseProductLicense `json:"activeLicense"`
|
||||
}
|
||||
|
||||
func (v *ListProductSubscriptionFields) MarshalJSON() ([]byte, error) {
|
||||
premarshaled, err := v.__premarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(premarshaled)
|
||||
}
|
||||
|
||||
func (v *ListProductSubscriptionFields) __premarshalJSON() (*__premarshalListProductSubscriptionFields, error) {
|
||||
var retval __premarshalListProductSubscriptionFields
|
||||
|
||||
retval.SourcegraphAccessTokens = v.SourcegraphAccessTokens
|
||||
retval.Id = v.ProductSubscriptionState.Id
|
||||
retval.Uuid = v.ProductSubscriptionState.Uuid
|
||||
retval.Account = v.ProductSubscriptionState.Account
|
||||
retval.IsArchived = v.ProductSubscriptionState.IsArchived
|
||||
retval.CodyGatewayAccess = v.ProductSubscriptionState.CodyGatewayAccess
|
||||
retval.ActiveLicense = v.ProductSubscriptionState.ActiveLicense
|
||||
return &retval, nil
|
||||
}
|
||||
|
||||
// ListProductSubscriptionsDotcomDotcomQuery includes the requested fields of the GraphQL type DotcomQuery.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// Mutations that are only used on Sourcegraph.com.
|
||||
// FOR INTERNAL USE ONLY.
|
||||
type ListProductSubscriptionsDotcomDotcomQuery struct {
|
||||
// A list of product subscriptions.
|
||||
// FOR INTERNAL USE ONLY.
|
||||
ProductSubscriptions ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnection `json:"productSubscriptions"`
|
||||
}
|
||||
|
||||
// GetProductSubscriptions returns ListProductSubscriptionsDotcomDotcomQuery.ProductSubscriptions, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQuery) GetProductSubscriptions() ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnection {
|
||||
return v.ProductSubscriptions
|
||||
}
|
||||
|
||||
// ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnection includes the requested fields of the GraphQL type ProductSubscriptionConnection.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// A list of product subscriptions.
|
||||
// FOR INTERNAL USE ONLY.
|
||||
type ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnection struct {
|
||||
// The total count of product subscriptions in the connection. This total count may be larger than the number of
|
||||
// nodes in this object when the result is paginated.
|
||||
TotalCount int `json:"totalCount"`
|
||||
// Pagination information.
|
||||
PageInfo ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionPageInfo `json:"pageInfo"`
|
||||
// A list of product subscriptions.
|
||||
Nodes []ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription `json:"nodes"`
|
||||
}
|
||||
|
||||
// GetTotalCount returns ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnection.TotalCount, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnection) GetTotalCount() int {
|
||||
return v.TotalCount
|
||||
}
|
||||
|
||||
// GetPageInfo returns ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnection.PageInfo, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnection) GetPageInfo() ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionPageInfo {
|
||||
return v.PageInfo
|
||||
}
|
||||
|
||||
// GetNodes returns ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnection.Nodes, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnection) GetNodes() []ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription {
|
||||
return v.Nodes
|
||||
}
|
||||
|
||||
// ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription includes the requested fields of the GraphQL type ProductSubscription.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// A product subscription that was created on Sourcegraph.com.
|
||||
// FOR INTERNAL USE ONLY.
|
||||
type ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription struct {
|
||||
ListProductSubscriptionFields `json:"-"`
|
||||
}
|
||||
|
||||
// GetSourcegraphAccessTokens returns ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription.SourcegraphAccessTokens, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription) GetSourcegraphAccessTokens() []string {
|
||||
return v.ListProductSubscriptionFields.SourcegraphAccessTokens
|
||||
}
|
||||
|
||||
// GetId returns ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription.Id, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription) GetId() string {
|
||||
return v.ListProductSubscriptionFields.ProductSubscriptionState.Id
|
||||
}
|
||||
|
||||
// GetUuid returns ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription.Uuid, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription) GetUuid() string {
|
||||
return v.ListProductSubscriptionFields.ProductSubscriptionState.Uuid
|
||||
}
|
||||
|
||||
// GetAccount returns ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription.Account, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription) GetAccount() *ProductSubscriptionStateAccountUser {
|
||||
return v.ListProductSubscriptionFields.ProductSubscriptionState.Account
|
||||
}
|
||||
|
||||
// GetIsArchived returns ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription.IsArchived, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription) GetIsArchived() bool {
|
||||
return v.ListProductSubscriptionFields.ProductSubscriptionState.IsArchived
|
||||
}
|
||||
|
||||
// GetCodyGatewayAccess returns ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription.CodyGatewayAccess, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription) GetCodyGatewayAccess() ProductSubscriptionStateCodyGatewayAccess {
|
||||
return v.ListProductSubscriptionFields.ProductSubscriptionState.CodyGatewayAccess
|
||||
}
|
||||
|
||||
// GetActiveLicense returns ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription.ActiveLicense, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription) GetActiveLicense() *ProductSubscriptionStateActiveLicenseProductLicense {
|
||||
return v.ListProductSubscriptionFields.ProductSubscriptionState.ActiveLicense
|
||||
}
|
||||
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription) UnmarshalJSON(b []byte) error {
|
||||
|
||||
if string(b) == "null" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var firstPass struct {
|
||||
*ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription
|
||||
graphql.NoUnmarshalJSON
|
||||
}
|
||||
firstPass.ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription = v
|
||||
|
||||
err := json.Unmarshal(b, &firstPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(
|
||||
b, &v.ListProductSubscriptionFields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type __premarshalListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription struct {
|
||||
SourcegraphAccessTokens []string `json:"sourcegraphAccessTokens"`
|
||||
|
||||
Id string `json:"id"`
|
||||
|
||||
Uuid string `json:"uuid"`
|
||||
|
||||
Account *ProductSubscriptionStateAccountUser `json:"account"`
|
||||
|
||||
IsArchived bool `json:"isArchived"`
|
||||
|
||||
CodyGatewayAccess ProductSubscriptionStateCodyGatewayAccess `json:"codyGatewayAccess"`
|
||||
|
||||
ActiveLicense *ProductSubscriptionStateActiveLicenseProductLicense `json:"activeLicense"`
|
||||
}
|
||||
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription) MarshalJSON() ([]byte, error) {
|
||||
premarshaled, err := v.__premarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(premarshaled)
|
||||
}
|
||||
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription) __premarshalJSON() (*__premarshalListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription, error) {
|
||||
var retval __premarshalListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionNodesProductSubscription
|
||||
|
||||
retval.SourcegraphAccessTokens = v.ListProductSubscriptionFields.SourcegraphAccessTokens
|
||||
retval.Id = v.ListProductSubscriptionFields.ProductSubscriptionState.Id
|
||||
retval.Uuid = v.ListProductSubscriptionFields.ProductSubscriptionState.Uuid
|
||||
retval.Account = v.ListProductSubscriptionFields.ProductSubscriptionState.Account
|
||||
retval.IsArchived = v.ListProductSubscriptionFields.ProductSubscriptionState.IsArchived
|
||||
retval.CodyGatewayAccess = v.ListProductSubscriptionFields.ProductSubscriptionState.CodyGatewayAccess
|
||||
retval.ActiveLicense = v.ListProductSubscriptionFields.ProductSubscriptionState.ActiveLicense
|
||||
return &retval, nil
|
||||
}
|
||||
|
||||
// ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionPageInfo includes the requested fields of the GraphQL type PageInfo.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// Pagination information for forward-only pagination. See https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo.
|
||||
type ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionPageInfo struct {
|
||||
// When paginating forwards, the cursor to continue.
|
||||
EndCursor *string `json:"endCursor"`
|
||||
// When paginating forwards, are there more items?
|
||||
HasNextPage bool `json:"hasNextPage"`
|
||||
}
|
||||
|
||||
// GetEndCursor returns ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionPageInfo.EndCursor, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionPageInfo) GetEndCursor() *string {
|
||||
return v.EndCursor
|
||||
}
|
||||
|
||||
// GetHasNextPage returns ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionPageInfo.HasNextPage, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsDotcomDotcomQueryProductSubscriptionsProductSubscriptionConnectionPageInfo) GetHasNextPage() bool {
|
||||
return v.HasNextPage
|
||||
}
|
||||
|
||||
// ListProductSubscriptionsResponse is returned by ListProductSubscriptions on success.
|
||||
type ListProductSubscriptionsResponse struct {
|
||||
// Queries that are only used on Sourcegraph.com.
|
||||
//
|
||||
// FOR INTERNAL USE ONLY.
|
||||
Dotcom ListProductSubscriptionsDotcomDotcomQuery `json:"dotcom"`
|
||||
}
|
||||
|
||||
// GetDotcom returns ListProductSubscriptionsResponse.Dotcom, and is useful for accessing the field via an interface.
|
||||
func (v *ListProductSubscriptionsResponse) GetDotcom() ListProductSubscriptionsDotcomDotcomQuery {
|
||||
return v.Dotcom
|
||||
}
|
||||
|
||||
// ProductSubscriptionState includes the GraphQL fields of ProductSubscription requested by the fragment ProductSubscriptionState.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// A product subscription that was created on Sourcegraph.com.
|
||||
// FOR INTERNAL USE ONLY.
|
||||
type ProductSubscriptionState struct {
|
||||
// The unique ID of this product subscription.
|
||||
Id string `json:"id"`
|
||||
// The unique UUID of this product subscription. Unlike ProductSubscription.id, this does not
|
||||
// encode the type and is not a GraphQL node ID.
|
||||
Uuid string `json:"uuid"`
|
||||
// The user (i.e., customer) to whom this subscription is granted, or null if the account has been deleted.
|
||||
Account *ProductSubscriptionStateAccountUser `json:"account"`
|
||||
// Whether this product subscription was archived.
|
||||
IsArchived bool `json:"isArchived"`
|
||||
// Cody Gateway access granted to this subscription. Properties may be inferred from the active license, or be defined in overrides.
|
||||
CodyGatewayAccess ProductSubscriptionStateCodyGatewayAccess `json:"codyGatewayAccess"`
|
||||
// The currently active product license associated with this product subscription, if any.
|
||||
ActiveLicense *ProductSubscriptionStateActiveLicenseProductLicense `json:"activeLicense"`
|
||||
}
|
||||
|
||||
// GetId returns ProductSubscriptionState.Id, and is useful for accessing the field via an interface.
|
||||
func (v *ProductSubscriptionState) GetId() string { return v.Id }
|
||||
|
||||
// GetUuid returns ProductSubscriptionState.Uuid, and is useful for accessing the field via an interface.
|
||||
func (v *ProductSubscriptionState) GetUuid() string { return v.Uuid }
|
||||
|
||||
// GetAccount returns ProductSubscriptionState.Account, and is useful for accessing the field via an interface.
|
||||
func (v *ProductSubscriptionState) GetAccount() *ProductSubscriptionStateAccountUser {
|
||||
return v.Account
|
||||
}
|
||||
|
||||
// GetIsArchived returns ProductSubscriptionState.IsArchived, and is useful for accessing the field via an interface.
|
||||
func (v *ProductSubscriptionState) GetIsArchived() bool { return v.IsArchived }
|
||||
|
||||
// GetCodyGatewayAccess returns ProductSubscriptionState.CodyGatewayAccess, and is useful for accessing the field via an interface.
|
||||
func (v *ProductSubscriptionState) GetCodyGatewayAccess() ProductSubscriptionStateCodyGatewayAccess {
|
||||
return v.CodyGatewayAccess
|
||||
}
|
||||
|
||||
// GetActiveLicense returns ProductSubscriptionState.ActiveLicense, and is useful for accessing the field via an interface.
|
||||
func (v *ProductSubscriptionState) GetActiveLicense() *ProductSubscriptionStateActiveLicenseProductLicense {
|
||||
return v.ActiveLicense
|
||||
}
|
||||
|
||||
// ProductSubscriptionStateAccountUser includes the requested fields of the GraphQL type User.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// A user.
|
||||
type ProductSubscriptionStateAccountUser struct {
|
||||
// The user's username.
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// GetUsername returns ProductSubscriptionStateAccountUser.Username, and is useful for accessing the field via an interface.
|
||||
func (v *ProductSubscriptionStateAccountUser) GetUsername() string { return v.Username }
|
||||
|
||||
// ProductSubscriptionStateActiveLicenseProductLicense includes the requested fields of the GraphQL type ProductLicense.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// A product license that was created on Sourcegraph.com.
|
||||
// FOR INTERNAL USE ONLY.
|
||||
type ProductSubscriptionStateActiveLicenseProductLicense struct {
|
||||
// Information about this product license.
|
||||
Info *ProductSubscriptionStateActiveLicenseProductLicenseInfo `json:"info"`
|
||||
}
|
||||
|
||||
// GetInfo returns ProductSubscriptionStateActiveLicenseProductLicense.Info, and is useful for accessing the field via an interface.
|
||||
func (v *ProductSubscriptionStateActiveLicenseProductLicense) GetInfo() *ProductSubscriptionStateActiveLicenseProductLicenseInfo {
|
||||
return v.Info
|
||||
}
|
||||
|
||||
// ProductSubscriptionStateActiveLicenseProductLicenseInfo includes the requested fields of the GraphQL type ProductLicenseInfo.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// Information about this site's product license (which activates certain Sourcegraph features).
|
||||
type ProductSubscriptionStateActiveLicenseProductLicenseInfo struct {
|
||||
// Tags indicating the product plan and features activated by this license.
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
// GetTags returns ProductSubscriptionStateActiveLicenseProductLicenseInfo.Tags, and is useful for accessing the field via an interface.
|
||||
func (v *ProductSubscriptionStateActiveLicenseProductLicenseInfo) GetTags() []string { return v.Tags }
|
||||
|
||||
// ProductSubscriptionStateCodyGatewayAccess includes the requested fields of the GraphQL type CodyGatewayAccess.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// Cody Gateway access granted to a subscription.
|
||||
// FOR INTERNAL USE ONLY.
|
||||
type ProductSubscriptionStateCodyGatewayAccess struct {
|
||||
CodyGatewayAccessFields `json:"-"`
|
||||
}
|
||||
|
||||
// GetEnabled returns ProductSubscriptionStateCodyGatewayAccess.Enabled, and is useful for accessing the field via an interface.
|
||||
func (v *ProductSubscriptionStateCodyGatewayAccess) GetEnabled() bool {
|
||||
return v.CodyGatewayAccessFields.Enabled
|
||||
}
|
||||
|
||||
// GetChatCompletionsRateLimit returns ProductSubscriptionStateCodyGatewayAccess.ChatCompletionsRateLimit, and is useful for accessing the field via an interface.
|
||||
func (v *ProductSubscriptionStateCodyGatewayAccess) GetChatCompletionsRateLimit() *CodyGatewayAccessFieldsChatCompletionsRateLimitCodyGatewayRateLimit {
|
||||
return v.CodyGatewayAccessFields.ChatCompletionsRateLimit
|
||||
}
|
||||
|
||||
// GetCodeCompletionsRateLimit returns ProductSubscriptionStateCodyGatewayAccess.CodeCompletionsRateLimit, and is useful for accessing the field via an interface.
|
||||
func (v *ProductSubscriptionStateCodyGatewayAccess) GetCodeCompletionsRateLimit() *CodyGatewayAccessFieldsCodeCompletionsRateLimitCodyGatewayRateLimit {
|
||||
return v.CodyGatewayAccessFields.CodeCompletionsRateLimit
|
||||
}
|
||||
|
||||
// GetEmbeddingsRateLimit returns ProductSubscriptionStateCodyGatewayAccess.EmbeddingsRateLimit, and is useful for accessing the field via an interface.
|
||||
func (v *ProductSubscriptionStateCodyGatewayAccess) GetEmbeddingsRateLimit() *CodyGatewayAccessFieldsEmbeddingsRateLimitCodyGatewayRateLimit {
|
||||
return v.CodyGatewayAccessFields.EmbeddingsRateLimit
|
||||
}
|
||||
|
||||
func (v *ProductSubscriptionStateCodyGatewayAccess) UnmarshalJSON(b []byte) error {
|
||||
|
||||
if string(b) == "null" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var firstPass struct {
|
||||
*ProductSubscriptionStateCodyGatewayAccess
|
||||
graphql.NoUnmarshalJSON
|
||||
}
|
||||
firstPass.ProductSubscriptionStateCodyGatewayAccess = v
|
||||
|
||||
err := json.Unmarshal(b, &firstPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(
|
||||
b, &v.CodyGatewayAccessFields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type __premarshalProductSubscriptionStateCodyGatewayAccess struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
ChatCompletionsRateLimit *CodyGatewayAccessFieldsChatCompletionsRateLimitCodyGatewayRateLimit `json:"chatCompletionsRateLimit"`
|
||||
|
||||
CodeCompletionsRateLimit *CodyGatewayAccessFieldsCodeCompletionsRateLimitCodyGatewayRateLimit `json:"codeCompletionsRateLimit"`
|
||||
|
||||
EmbeddingsRateLimit *CodyGatewayAccessFieldsEmbeddingsRateLimitCodyGatewayRateLimit `json:"embeddingsRateLimit"`
|
||||
}
|
||||
|
||||
func (v *ProductSubscriptionStateCodyGatewayAccess) MarshalJSON() ([]byte, error) {
|
||||
premarshaled, err := v.__premarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(premarshaled)
|
||||
}
|
||||
|
||||
func (v *ProductSubscriptionStateCodyGatewayAccess) __premarshalJSON() (*__premarshalProductSubscriptionStateCodyGatewayAccess, error) {
|
||||
var retval __premarshalProductSubscriptionStateCodyGatewayAccess
|
||||
|
||||
retval.Enabled = v.CodyGatewayAccessFields.Enabled
|
||||
retval.ChatCompletionsRateLimit = v.CodyGatewayAccessFields.ChatCompletionsRateLimit
|
||||
retval.CodeCompletionsRateLimit = v.CodyGatewayAccessFields.CodeCompletionsRateLimit
|
||||
retval.EmbeddingsRateLimit = v.CodyGatewayAccessFields.EmbeddingsRateLimit
|
||||
return &retval, nil
|
||||
}
|
||||
|
||||
// RateLimitFields includes the GraphQL fields of CodyGatewayRateLimit requested by the fragment RateLimitFields.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
@ -1203,14 +617,6 @@ func (v *SnippetAttributionSnippetAttributionSnippetAttributionConnectionNodesSn
|
||||
return v.RepositoryName
|
||||
}
|
||||
|
||||
// __CheckAccessTokenInput is used internally by genqlient
|
||||
type __CheckAccessTokenInput struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// GetToken returns __CheckAccessTokenInput.Token, and is useful for accessing the field via an interface.
|
||||
func (v *__CheckAccessTokenInput) GetToken() string { return v.Token }
|
||||
|
||||
// __CheckDotcomUserAccessTokenInput is used internally by genqlient
|
||||
type __CheckDotcomUserAccessTokenInput struct {
|
||||
Token string `json:"token"`
|
||||
@ -1231,76 +637,6 @@ func (v *__SnippetAttributionInput) GetSnippet() string { return v.Snippet }
|
||||
// GetFirst returns __SnippetAttributionInput.First, and is useful for accessing the field via an interface.
|
||||
func (v *__SnippetAttributionInput) GetFirst() int { return v.First }
|
||||
|
||||
// CheckAccessToken returns traits of the product subscription associated with
|
||||
// the given access token.
|
||||
func CheckAccessToken(
|
||||
ctx context.Context,
|
||||
client graphql.Client,
|
||||
token string,
|
||||
) (*CheckAccessTokenResponse, error) {
|
||||
req := &graphql.Request{
|
||||
OpName: "CheckAccessToken",
|
||||
Query: `
|
||||
query CheckAccessToken ($token: String!) {
|
||||
dotcom {
|
||||
productSubscriptionByAccessToken(accessToken: $token) {
|
||||
... ProductSubscriptionState
|
||||
}
|
||||
}
|
||||
}
|
||||
fragment ProductSubscriptionState on ProductSubscription {
|
||||
id
|
||||
uuid
|
||||
account {
|
||||
username
|
||||
}
|
||||
isArchived
|
||||
codyGatewayAccess {
|
||||
... CodyGatewayAccessFields
|
||||
}
|
||||
activeLicense {
|
||||
info {
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
||||
fragment CodyGatewayAccessFields on CodyGatewayAccess {
|
||||
enabled
|
||||
chatCompletionsRateLimit {
|
||||
... RateLimitFields
|
||||
}
|
||||
codeCompletionsRateLimit {
|
||||
... RateLimitFields
|
||||
}
|
||||
embeddingsRateLimit {
|
||||
... RateLimitFields
|
||||
}
|
||||
}
|
||||
fragment RateLimitFields on CodyGatewayRateLimit {
|
||||
allowedModels
|
||||
source
|
||||
limit
|
||||
intervalSeconds
|
||||
}
|
||||
`,
|
||||
Variables: &__CheckAccessTokenInput{
|
||||
Token: token,
|
||||
},
|
||||
}
|
||||
var err error
|
||||
|
||||
var data CheckAccessTokenResponse
|
||||
resp := &graphql.Response{Data: &data}
|
||||
|
||||
err = client.MakeRequest(
|
||||
ctx,
|
||||
req,
|
||||
resp,
|
||||
)
|
||||
|
||||
return &data, err
|
||||
}
|
||||
|
||||
// CheckDotcomUserAccessToken returns traits of the product subscription associated with
|
||||
// the given access token.
|
||||
func CheckDotcomUserAccessToken(
|
||||
@ -1362,81 +698,6 @@ fragment RateLimitFields on CodyGatewayRateLimit {
|
||||
return &data, err
|
||||
}
|
||||
|
||||
func ListProductSubscriptions(
|
||||
ctx context.Context,
|
||||
client graphql.Client,
|
||||
) (*ListProductSubscriptionsResponse, error) {
|
||||
req := &graphql.Request{
|
||||
OpName: "ListProductSubscriptions",
|
||||
Query: `
|
||||
query ListProductSubscriptions {
|
||||
dotcom {
|
||||
productSubscriptions {
|
||||
totalCount
|
||||
pageInfo {
|
||||
endCursor
|
||||
hasNextPage
|
||||
}
|
||||
nodes {
|
||||
... ListProductSubscriptionFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fragment ListProductSubscriptionFields on ProductSubscription {
|
||||
... ProductSubscriptionState
|
||||
sourcegraphAccessTokens
|
||||
}
|
||||
fragment ProductSubscriptionState on ProductSubscription {
|
||||
id
|
||||
uuid
|
||||
account {
|
||||
username
|
||||
}
|
||||
isArchived
|
||||
codyGatewayAccess {
|
||||
... CodyGatewayAccessFields
|
||||
}
|
||||
activeLicense {
|
||||
info {
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
||||
fragment CodyGatewayAccessFields on CodyGatewayAccess {
|
||||
enabled
|
||||
chatCompletionsRateLimit {
|
||||
... RateLimitFields
|
||||
}
|
||||
codeCompletionsRateLimit {
|
||||
... RateLimitFields
|
||||
}
|
||||
embeddingsRateLimit {
|
||||
... RateLimitFields
|
||||
}
|
||||
}
|
||||
fragment RateLimitFields on CodyGatewayRateLimit {
|
||||
allowedModels
|
||||
source
|
||||
limit
|
||||
intervalSeconds
|
||||
}
|
||||
`,
|
||||
}
|
||||
var err error
|
||||
|
||||
var data ListProductSubscriptionsResponse
|
||||
resp := &graphql.Response{Data: &data}
|
||||
|
||||
err = client.MakeRequest(
|
||||
ctx,
|
||||
req,
|
||||
resp,
|
||||
)
|
||||
|
||||
return &data, err
|
||||
}
|
||||
|
||||
// Searches the instances indexed code for code matching snippet.
|
||||
func SnippetAttribution(
|
||||
ctx context.Context,
|
||||
|
||||
@ -18,53 +18,6 @@ fragment CodyGatewayAccessFields on CodyGatewayAccess {
|
||||
}
|
||||
}
|
||||
|
||||
fragment ProductSubscriptionState on ProductSubscription {
|
||||
id
|
||||
uuid
|
||||
account {
|
||||
username
|
||||
}
|
||||
isArchived
|
||||
codyGatewayAccess {
|
||||
...CodyGatewayAccessFields
|
||||
}
|
||||
activeLicense {
|
||||
info {
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# CheckAccessToken returns traits of the product subscription associated with
|
||||
# the given access token.
|
||||
query CheckAccessToken($token: String!) {
|
||||
dotcom {
|
||||
productSubscriptionByAccessToken(accessToken: $token) {
|
||||
...ProductSubscriptionState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment ListProductSubscriptionFields on ProductSubscription {
|
||||
...ProductSubscriptionState
|
||||
sourcegraphAccessTokens
|
||||
}
|
||||
|
||||
query ListProductSubscriptions {
|
||||
dotcom {
|
||||
productSubscriptions {
|
||||
totalCount
|
||||
pageInfo {
|
||||
endCursor
|
||||
hasNextPage
|
||||
}
|
||||
nodes {
|
||||
...ListProductSubscriptionFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment DotcomUserState on CodyGatewayDotcomUser {
|
||||
id
|
||||
username
|
||||
|
||||
@ -35,6 +35,7 @@ go_library(
|
||||
"//internal/trace",
|
||||
"//internal/version",
|
||||
"//lib/errors",
|
||||
"//lib/pointers",
|
||||
"@com_github_gorilla_mux//:mux",
|
||||
"@com_github_khan_genqlient//graphql",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
|
||||
@ -27,7 +27,7 @@ func NewHandler(client graphql.Client, baseLogger log.Logger) http.Handler {
|
||||
ctx := r.Context()
|
||||
a := actor.FromContext(ctx)
|
||||
logger := a.Logger(trace.Logger(ctx, baseLogger))
|
||||
if got, want := a.GetSource(), codygateway.ActorSourceProductSubscription; got != want {
|
||||
if got, want := a.GetSource(), codygateway.ActorSourceEnterpriseSubscription; got != want {
|
||||
response.JSONError(logger, w, http.StatusUnauthorized, errors.New("only available for enterprise product subscriptions"))
|
||||
return
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ func request(t *testing.T) *http.Request {
|
||||
func TestSuccess(t *testing.T) {
|
||||
logger := logtest.Scoped(t)
|
||||
ps := fakeActorSource{
|
||||
name: codygateway.ActorSourceProductSubscription,
|
||||
name: codygateway.ActorSourceEnterpriseSubscription,
|
||||
}
|
||||
authr := &auth.Authenticator{
|
||||
Sources: actor.NewSources(ps),
|
||||
@ -177,7 +177,7 @@ func TestFailsForDotcomUsers(t *testing.T) {
|
||||
func TestUnavailableIfConfigDisabled(t *testing.T) {
|
||||
logger := logtest.Scoped(t)
|
||||
dotCom := fakeActorSource{
|
||||
name: codygateway.ActorSourceProductSubscription,
|
||||
name: codygateway.ActorSourceEnterpriseSubscription,
|
||||
}
|
||||
authr := &auth.Authenticator{
|
||||
Sources: actor.NewSources(dotCom),
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/instrumentation"
|
||||
"github.com/sourcegraph/sourcegraph/internal/redispool"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/sourcegraph/sourcegraph/lib/pointers"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/sams"
|
||||
)
|
||||
@ -29,23 +30,28 @@ const samsScopeFlaggedPromptRead = "cody_gateway::flaggedprompts::read"
|
||||
func NewMaintenanceHandler(
|
||||
baseLogger log.Logger, next http.Handler, config *config.Config, redisKV redispool.KeyValue) http.Handler {
|
||||
// Do nothing if no SAMS configuration is provided.
|
||||
if !config.SAMSClientConfig.Valid() {
|
||||
baseLogger.Warn("No SAMS client config provided. Not registering maintenance endpoints.")
|
||||
if err := config.SAMSClientConfig.Validate(); err != nil {
|
||||
baseLogger.Warn("no SAMS client config provided; not registering maintenance endpoints",
|
||||
log.Error(err))
|
||||
return next
|
||||
}
|
||||
|
||||
logger := baseLogger.Scoped("Maintenance")
|
||||
|
||||
// Create the SAMS Client.
|
||||
samsAPIURL := pointers.Deref(
|
||||
config.SAMSClientConfig.ConnConfig.APIURL,
|
||||
config.SAMSClientConfig.ConnConfig.ExternalURL, // default
|
||||
)
|
||||
samsClient := sams.NewClient(
|
||||
config.SAMSClientConfig.URL,
|
||||
samsAPIURL,
|
||||
clientcredentials.Config{
|
||||
ClientID: config.SAMSClientConfig.ClientID,
|
||||
ClientSecret: config.SAMSClientConfig.ClientSecret,
|
||||
// Since we are only using our SAMS client to verify supplied token,
|
||||
// we just issue tokens with a minimal set of scopes.
|
||||
Scopes: []string{"openid", "profile", "email"},
|
||||
TokenURL: fmt.Sprintf("%s/oauth/token", config.SAMSClientConfig.URL),
|
||||
TokenURL: fmt.Sprintf("%s/oauth/token", samsAPIURL),
|
||||
})
|
||||
|
||||
return newMaintenanceHandler(logger, next, redisKV, samsClient)
|
||||
|
||||
@ -150,7 +150,7 @@ func handleNotify(
|
||||
|
||||
var actorLink string
|
||||
switch actor.GetSource() {
|
||||
case codygateway.ActorSourceProductSubscription:
|
||||
case codygateway.ActorSourceEnterpriseSubscription:
|
||||
actorLink = fmt.Sprintf("<%s/site-admin/dotcom/product/subscriptions/%s|%s>", dotcomURL, actor.GetID(), actor.GetName())
|
||||
default:
|
||||
actorLink = fmt.Sprintf("`%s`", actor.GetID())
|
||||
|
||||
@ -16,13 +16,13 @@ import (
|
||||
|
||||
func TestThresholds(t *testing.T) {
|
||||
th := Thresholds{
|
||||
codygateway.ActorSourceDotcomUser: []int{100},
|
||||
codygateway.ActorSourceProductSubscription: []int{100, 90},
|
||||
codygateway.ActorSourceDotcomUser: []int{100},
|
||||
codygateway.ActorSourceEnterpriseSubscription: []int{100, 90},
|
||||
}
|
||||
// Explicitly configured
|
||||
autogold.Expect([]int{100}).Equal(t, th.Get(codygateway.ActorSourceDotcomUser))
|
||||
// Sorted
|
||||
autogold.Expect([]int{90, 100}).Equal(t, th.Get(codygateway.ActorSourceProductSubscription))
|
||||
autogold.Expect([]int{90, 100}).Equal(t, th.Get(codygateway.ActorSourceEnterpriseSubscription))
|
||||
// Defaults
|
||||
autogold.Expect([]int{}).Equal(t, th.Get(codygateway.ActorSource("anonymous")))
|
||||
}
|
||||
@ -92,7 +92,7 @@ func TestSlackRateLimitNotifier(t *testing.T) {
|
||||
logger,
|
||||
test.mockRedis(t),
|
||||
"https://sourcegraph.com/",
|
||||
Thresholds{codygateway.ActorSourceProductSubscription: []int{50, 80, 90}},
|
||||
Thresholds{codygateway.ActorSourceEnterpriseSubscription: []int{50, 80, 90}},
|
||||
"https://hooks.slack.com",
|
||||
func(ctx context.Context, url string, msg *slack.WebhookMessage) error {
|
||||
alerted = true
|
||||
@ -104,7 +104,7 @@ func TestSlackRateLimitNotifier(t *testing.T) {
|
||||
&mockActor{
|
||||
id: "foobar",
|
||||
name: "alice",
|
||||
source: codygateway.ActorSourceProductSubscription,
|
||||
source: codygateway.ActorSourceEnterpriseSubscription,
|
||||
},
|
||||
codygateway.FeatureChatCompletions,
|
||||
test.usageRatio,
|
||||
|
||||
@ -16,6 +16,7 @@ go_library(
|
||||
"//cmd/cody-gateway/internal/actor/anonymous",
|
||||
"//cmd/cody-gateway/internal/actor/dotcomuser",
|
||||
"//cmd/cody-gateway/internal/actor/productsubscription",
|
||||
"//cmd/cody-gateway/internal/actor/productsubscription/enterpriseportal",
|
||||
"//cmd/cody-gateway/internal/auth",
|
||||
"//cmd/cody-gateway/internal/dotcom",
|
||||
"//cmd/cody-gateway/internal/events",
|
||||
@ -42,6 +43,8 @@ go_library(
|
||||
"//internal/tracer/oteldefaults",
|
||||
"//internal/tracer/oteldefaults/exporters",
|
||||
"//internal/version",
|
||||
"//lib/background",
|
||||
"//lib/enterpriseportal/codyaccess/v1:codyaccess",
|
||||
"//lib/errors",
|
||||
"@com_github_go_redsync_redsync_v4//:redsync",
|
||||
"@com_github_go_redsync_redsync_v4//redis/redigo",
|
||||
@ -52,6 +55,8 @@ go_library(
|
||||
"@com_github_slack_go_slack//:slack",
|
||||
"@com_github_sourcegraph_conc//:conc",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//:sourcegraph-accounts-sdk-go",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//scopes",
|
||||
"@io_opentelemetry_go_contrib_detectors_gcp//:gcp",
|
||||
"@io_opentelemetry_go_contrib_instrumentation_net_http_otelhttp//:otelhttp",
|
||||
"@io_opentelemetry_go_otel//:otel",
|
||||
|
||||
@ -16,5 +16,6 @@ go_library(
|
||||
"//internal/env",
|
||||
"//internal/trace/policy",
|
||||
"//lib/errors",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//:sourcegraph-accounts-sdk-go",
|
||||
],
|
||||
)
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/httpapi/embeddings"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codygateway"
|
||||
"github.com/sourcegraph/sourcegraph/internal/collections"
|
||||
@ -30,7 +31,6 @@ type Config struct {
|
||||
Dotcom struct {
|
||||
URL string
|
||||
AccessToken string
|
||||
InternalMode bool
|
||||
ActorRefreshCoolDownInterval time.Duration
|
||||
|
||||
// Prompts that get flagged are stored in Redis for a short-time, for
|
||||
@ -40,6 +40,10 @@ type Config struct {
|
||||
ClientID string
|
||||
}
|
||||
|
||||
EnterprisePortal struct {
|
||||
URL *url.URL
|
||||
}
|
||||
|
||||
Anthropic AnthropicConfig
|
||||
|
||||
OpenAI OpenAIConfig
|
||||
@ -162,13 +166,19 @@ type FlaggingConfig struct {
|
||||
}
|
||||
|
||||
type SAMSClientConfig struct {
|
||||
URL string
|
||||
ConnConfig sams.ConnConfig
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
}
|
||||
|
||||
func (scc SAMSClientConfig) Valid() bool {
|
||||
return !(scc.URL == "" || scc.ClientID == "" || scc.ClientSecret == "")
|
||||
func (sams SAMSClientConfig) Validate() error {
|
||||
if err := sams.ConnConfig.Validate(); err != nil {
|
||||
return errors.Wrap(err, "invalid ConnConfig")
|
||||
}
|
||||
if sams.ClientID == "" || sams.ClientSecret == "" {
|
||||
return errors.New("ClientID and ClientSecret must be set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) Load() {
|
||||
@ -179,20 +189,28 @@ func (c *Config) Load() {
|
||||
"should be used as 'Authorization: Bearer $secret' header when accessing diagnostics endpoints.")
|
||||
|
||||
c.Dotcom.AccessToken = c.GetOptional("CODY_GATEWAY_DOTCOM_ACCESS_TOKEN",
|
||||
"The Sourcegraph.com access token to be used. If not provided, dotcom-based actor sources will be disabled.")
|
||||
"The Sourcegraph.com access token to be used. If not provided, the dotcom-user actor source will be disabled.")
|
||||
c.Dotcom.ClientID = c.GetOptional("CODY_GATEWAY_DOTCOM_CLIENT_ID",
|
||||
"Value of X-Sourcegraph-Client-Id header to be passed to sourcegraph.com.")
|
||||
c.Dotcom.URL = c.Get("CODY_GATEWAY_DOTCOM_API_URL", "https://sourcegraph.com/.api/graphql", "Custom override for the dotcom API endpoint")
|
||||
if _, err := url.Parse(c.Dotcom.URL); err != nil {
|
||||
c.AddError(errors.Wrap(err, "invalid CODY_GATEWAY_DOTCOM_API_URL"))
|
||||
}
|
||||
c.Dotcom.InternalMode = c.GetBool("CODY_GATEWAY_DOTCOM_INTERNAL_MODE", "false", "Only allow tokens associated with active internal and dev licenses to be used.") ||
|
||||
c.GetBool("CODY_GATEWAY_DOTCOM_DEV_LICENSES_ONLY", "false", "DEPRECATED, use CODY_GATEWAY_DOTCOM_INTERNAL_MODE")
|
||||
c.Dotcom.ActorRefreshCoolDownInterval = c.GetInterval("CODY_GATEWAY_DOTCOM_ACTOR_COOLDOWN_INTERVAL", "300s",
|
||||
"Cooldown period for refreshing the actor info from dotcom.")
|
||||
c.Dotcom.FlaggedPromptRecorderTTL = c.GetInterval("CODY_GATEWAY_DOTCOM_FLAGGED_PROMPT_RECORDER_TTL", "1h",
|
||||
"Period to retain prompts in Redis.")
|
||||
|
||||
enterprisePortalURL := c.GetOptional("CODY_GATEWAY_ENTERPRISE_PORTAL_URL",
|
||||
"The Enterprise Portal instance to target. If not provided, the product subscriptions actor source will be disabled.")
|
||||
if enterprisePortalURL != "" {
|
||||
var err error
|
||||
c.EnterprisePortal.URL, err = url.Parse(enterprisePortalURL)
|
||||
if err != nil {
|
||||
c.AddError(errors.Wrap(err, "invalid CODY_GATEWAY_ENTERPRISE_PORTAL_URL"))
|
||||
}
|
||||
}
|
||||
|
||||
c.Anthropic.AccessToken = c.Get("CODY_GATEWAY_ANTHROPIC_ACCESS_TOKEN", "", "The Anthropic access token to be used.")
|
||||
c.Anthropic.AllowedModels = splitMaybe(c.Get("CODY_GATEWAY_ANTHROPIC_ALLOWED_MODELS",
|
||||
strings.Join([]string{
|
||||
@ -343,7 +361,12 @@ func (c *Config) Load() {
|
||||
c.Sourcegraph.EmbeddingsAPIURL = c.Get("CODY_GATEWAY_SOURCEGRAPH_EMBEDDINGS_API_URL", "https://embeddings.sourcegraph.com/v2/models/st-multi-qa-mpnet-base-dot-v1/infer", "URL of the SMEGA API.")
|
||||
c.Sourcegraph.EmbeddingsAPIToken = c.Get("CODY_GATEWAY_SOURCEGRAPH_EMBEDDINGS_API_TOKEN", "", "Token to use for the SMEGA API.")
|
||||
|
||||
c.SAMSClientConfig.URL = c.GetOptional("SAMS_URL", "SAMS service endpoint")
|
||||
// SAMS_URL, SAMS_API_URL are same keys used for sams.NewConnConfigFromEnv
|
||||
c.SAMSClientConfig.ConnConfig.ExternalURL = c.Get("SAMS_URL", "https://accounts.sourcegraph.com",
|
||||
"SAMS external service endpoint")
|
||||
if apiurl := c.GetOptional("SAMS_API_URL", "SAMS API endpoint"); apiurl != "" {
|
||||
c.SAMSClientConfig.ConnConfig.APIURL = &apiurl
|
||||
}
|
||||
c.SAMSClientConfig.ClientID = c.GetOptional("SAMS_CLIENT_ID", "SAMS OAuth client ID")
|
||||
c.SAMSClientConfig.ClientSecret = c.GetOptional("SAMS_CLIENT_SECRET", "SAMS OAuth client secret")
|
||||
|
||||
|
||||
@ -23,14 +23,9 @@ import (
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/shared/config"
|
||||
sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
|
||||
"github.com/sourcegraph/sourcegraph-accounts-sdk-go/scopes"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/auth"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/events"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/httpapi/completions"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/httpapi/featurelimiter"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/limiter"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/notify"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codygateway"
|
||||
"github.com/sourcegraph/sourcegraph/internal/goroutine"
|
||||
"github.com/sourcegraph/sourcegraph/internal/httpcli"
|
||||
@ -43,14 +38,25 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/service"
|
||||
"github.com/sourcegraph/sourcegraph/internal/trace"
|
||||
"github.com/sourcegraph/sourcegraph/internal/version"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/lib/background"
|
||||
codyaccessv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/codyaccess/v1"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor/anonymous"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor/dotcomuser"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor/productsubscription"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor/productsubscription/enterpriseportal"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/auth"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/dotcom"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/events"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/httpapi"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/httpapi/completions"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/httpapi/featurelimiter"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/limiter"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/notify"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/shared/config"
|
||||
)
|
||||
|
||||
func Main(ctx context.Context, obctx *observation.Context, ready service.ReadyFunc, cfg *config.Config) error {
|
||||
@ -125,7 +131,7 @@ func Main(ctx context.Context, obctx *observation.Context, ready service.ReadyFu
|
||||
dotcomURL.String(),
|
||||
notify.Thresholds{
|
||||
// Detailed notifications for product subscriptions.
|
||||
codygateway.ActorSourceProductSubscription: []int{90, 95, 100},
|
||||
codygateway.ActorSourceEnterpriseSubscription: []int{90, 95, 100},
|
||||
// No notifications for individual dotcom users - this can get quite
|
||||
// spammy.
|
||||
codygateway.ActorSourceDotcomUser: []int{},
|
||||
@ -138,24 +144,63 @@ func Main(ctx context.Context, obctx *observation.Context, ready service.ReadyFu
|
||||
|
||||
// Supported actor/auth sources
|
||||
sources := actor.NewSources(anonymous.NewSource(cfg.AllowAnonymous, cfg.ActorConcurrencyLimit))
|
||||
|
||||
var dotcomClient graphql.Client
|
||||
if cfg.Dotcom.AccessToken != "" {
|
||||
// dotcom-based actor sources only if an access token is provided for
|
||||
// us to talk with the client
|
||||
obctx.Logger.Info("dotcom-based actor sources are enabled")
|
||||
obctx.Logger.Info("dotcom-user actor source enabled")
|
||||
dotcomClient = dotcom.NewClient(cfg.Dotcom.URL, cfg.Dotcom.AccessToken, cfg.Dotcom.ClientID, cfg.Environment)
|
||||
sources.Add(
|
||||
dotcomuser.NewSource(
|
||||
obctx.Logger,
|
||||
rcache.NewWithTTL(fmt.Sprintf("dotcom-users:%s", dotcomuser.SourceVersion), int(cfg.SourcesCacheTTL.Seconds())),
|
||||
dotcomClient,
|
||||
cfg.ActorConcurrencyLimit,
|
||||
rs,
|
||||
cfg.Dotcom.ActorRefreshCoolDownInterval,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
obctx.Logger.Error("CODY_GATEWAY_DOTCOM_ACCESS_TOKEN is not set, dotcom-user actor source is disabled")
|
||||
}
|
||||
|
||||
// backgroundRoutines collects background dependencies for shutdown
|
||||
var backgroundRoutines []background.Routine
|
||||
if cfg.EnterprisePortal.URL != nil {
|
||||
obctx.Logger.Info("enterprise subscriptions actor source enabled")
|
||||
conn, err := enterpriseportal.Dial(
|
||||
ctx,
|
||||
obctx.Logger.Scoped("enterpriseportal"),
|
||||
cfg.EnterprisePortal.URL,
|
||||
// Authenticate using SAMS client credentials
|
||||
sams.ClientCredentialsTokenSource(
|
||||
cfg.SAMSClientConfig.ConnConfig,
|
||||
cfg.SAMSClientConfig.ClientID,
|
||||
cfg.SAMSClientConfig.ClientSecret,
|
||||
[]scopes.Scope{
|
||||
scopes.ToScope(scopes.ServiceEnterprisePortal, "codyaccess", scopes.ActionRead),
|
||||
scopes.ToScope(scopes.ServiceEnterprisePortal, "subscription", scopes.ActionRead),
|
||||
},
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "connect to Enterprise Portal")
|
||||
}
|
||||
backgroundRoutines = append(backgroundRoutines, background.CallbackRoutine{
|
||||
NameFunc: func() string { return "enterpriseportal.grpc.conn" },
|
||||
StopFunc: func(context.Context) error { return conn.Close() },
|
||||
})
|
||||
sources.Add(
|
||||
productsubscription.NewSource(
|
||||
obctx.Logger,
|
||||
rcache.NewWithTTL(fmt.Sprintf("product-subscriptions:%s", productsubscription.SourceVersion), int(cfg.SourcesCacheTTL.Seconds())),
|
||||
dotcomClient,
|
||||
cfg.Dotcom.InternalMode,
|
||||
codyaccessv1.NewCodyAccessServiceClient(conn),
|
||||
cfg.ActorConcurrencyLimit,
|
||||
),
|
||||
dotcomuser.NewSource(obctx.Logger, rcache.NewWithTTL(fmt.Sprintf("dotcom-users:%s", dotcomuser.SourceVersion), int(cfg.SourcesCacheTTL.Seconds())), dotcomClient, cfg.ActorConcurrencyLimit, rs, cfg.Dotcom.ActorRefreshCoolDownInterval),
|
||||
)
|
||||
} else {
|
||||
obctx.Logger.Warn("CODY_GATEWAY_DOTCOM_ACCESS_TOKEN is not set, dotcom-based actor sources are disabled")
|
||||
obctx.Logger.Error("CODY_GATEWAY_ENTERPRISE_PORTAL_URL is not set, enterprise subscriptions actor source is disabled")
|
||||
}
|
||||
|
||||
authr := &auth.Authenticator{
|
||||
@ -227,17 +272,23 @@ func Main(ctx context.Context, obctx *observation.Context, ready service.ReadyFu
|
||||
ready()
|
||||
obctx.Logger.Info("service ready", log.String("address", address))
|
||||
|
||||
// Collect background routines
|
||||
backgroundRoutines := []goroutine.BackgroundRoutine{
|
||||
// Collect service routines
|
||||
serviceRoutines := []goroutine.BackgroundRoutine{
|
||||
server,
|
||||
sources.Worker(obctx, sourceWorkerMutex, cfg.SourcesSyncInterval),
|
||||
}
|
||||
if w, ok := eventLogger.(goroutine.BackgroundRoutine); ok {
|
||||
// eventLogger is events.BufferedLogger
|
||||
// eventLogger is events.BufferedLogger, we need to shut it down cleanly
|
||||
backgroundRoutines = append(backgroundRoutines, w)
|
||||
}
|
||||
|
||||
// Block until done
|
||||
return goroutine.MonitorBackgroundRoutines(ctx, backgroundRoutines...)
|
||||
return goroutine.MonitorBackgroundRoutines(ctx, background.FIFOSTopRoutine{
|
||||
// Stop important service routines first
|
||||
background.CombinedRoutine(serviceRoutines),
|
||||
// Then shut down other background services
|
||||
background.CombinedRoutine(backgroundRoutines),
|
||||
})
|
||||
}
|
||||
|
||||
func newRedisStore(store redispool.KeyValue) limiter.RedisStore {
|
||||
|
||||
@ -86,7 +86,7 @@ func (s *handlerV1) GetCodyGatewayAccess(ctx context.Context, req *connect.Reque
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("invalid query"))
|
||||
}
|
||||
if err != nil {
|
||||
if err == dotcomdb.ErrCodyGatewayAccessNotFound {
|
||||
if errors.Is(err, dotcomdb.ErrCodyGatewayAccessNotFound) {
|
||||
return nil, connect.NewError(connect.CodeNotFound, err)
|
||||
}
|
||||
return nil, connectutil.InternalError(ctx, logger, err,
|
||||
|
||||
@ -67,7 +67,7 @@ func (r codyGatewayAccessResolver) ChatCompletionsRateLimit(ctx context.Context)
|
||||
return &codyGatewayRateLimitResolver{
|
||||
feature: types.CompletionsFeatureChat,
|
||||
actorID: r.sub.UUID(),
|
||||
actorSource: codygateway.ActorSourceProductSubscription,
|
||||
actorSource: codygateway.ActorSourceEnterpriseSubscription,
|
||||
v: rateLimit,
|
||||
source: source,
|
||||
}, nil
|
||||
@ -107,7 +107,7 @@ func (r codyGatewayAccessResolver) CodeCompletionsRateLimit(ctx context.Context)
|
||||
return &codyGatewayRateLimitResolver{
|
||||
feature: types.CompletionsFeatureCode,
|
||||
actorID: r.sub.UUID(),
|
||||
actorSource: codygateway.ActorSourceProductSubscription,
|
||||
actorSource: codygateway.ActorSourceEnterpriseSubscription,
|
||||
v: rateLimit,
|
||||
source: source,
|
||||
}, nil
|
||||
@ -146,7 +146,7 @@ func (r codyGatewayAccessResolver) EmbeddingsRateLimit(ctx context.Context) (gra
|
||||
|
||||
return &codyGatewayRateLimitResolver{
|
||||
actorID: r.sub.UUID(),
|
||||
actorSource: codygateway.ActorSourceProductSubscription,
|
||||
actorSource: codygateway.ActorSourceEnterpriseSubscription,
|
||||
v: rateLimit,
|
||||
source: source,
|
||||
}, nil
|
||||
|
||||
@ -124,5 +124,6 @@ write_source_files(
|
||||
"//cmd/repo-updater/internal/gitserver:generate_mocks",
|
||||
"//dev/build-tracker:generate_mocks",
|
||||
"//cmd/worker/internal/sourcegraphaccounts:generate_mocks",
|
||||
"//cmd/cody-gateway/internal/actor/productsubscription/productsubscriptiontest:generate_mocks",
|
||||
],
|
||||
)
|
||||
|
||||
@ -3,8 +3,12 @@ package codygateway
|
||||
type ActorSource string
|
||||
|
||||
const (
|
||||
ActorSourceProductSubscription ActorSource = "dotcom-product-subscriptions"
|
||||
ActorSourceDotcomUser ActorSource = "dotcom-user"
|
||||
// We retain legacy naming just in case there are hard dependencies on this
|
||||
// name. Today, these are Enterprise Subscriptions sourced from the Enterprise
|
||||
// Portal service.
|
||||
ActorSourceEnterpriseSubscription ActorSource = "dotcom-product-subscriptions"
|
||||
// Sourcegraph.com user actors.
|
||||
ActorSourceDotcomUser ActorSource = "dotcom-user"
|
||||
)
|
||||
|
||||
const CompletionsEventFeatureMetadataField = "feature"
|
||||
|
||||
14
internal/grpc/grpcoauth/BUILD.bazel
Normal file
14
internal/grpc/grpcoauth/BUILD.bazel
Normal file
@ -0,0 +1,14 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "grpcoauth",
|
||||
srcs = ["grpcoauth.go"],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/internal/grpc/grpcoauth",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/env",
|
||||
"//lib/errors",
|
||||
"@org_golang_google_grpc//credentials",
|
||||
"@org_golang_x_oauth2//:oauth2",
|
||||
],
|
||||
)
|
||||
43
internal/grpc/grpcoauth/grpcoauth.go
Normal file
43
internal/grpc/grpcoauth/grpcoauth.go
Normal file
@ -0,0 +1,43 @@
|
||||
package grpcoauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/env"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
// TokenSource supplies PerRPCCredentials from an oauth2.TokenSource.
|
||||
// It is a fork of the implementation in "google.golang.org/grpc/credentials/oauth",
|
||||
// but checks "internal/env" to toggle the "must require secure transport"
|
||||
// behaviour. Without these changes, the upstream token source cannot be used
|
||||
// in local development.
|
||||
type TokenSource struct {
|
||||
oauth2.TokenSource
|
||||
}
|
||||
|
||||
// GetRequestMetadata gets the request metadata as a map from a TokenSource.
|
||||
func (ts TokenSource) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
|
||||
token, err := ts.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !env.InsecureDev {
|
||||
ri, _ := credentials.RequestInfoFromContext(ctx)
|
||||
if err = credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil {
|
||||
return nil, errors.Newf("unable to transfer TokenSource PerRPCCredentials: %w", err)
|
||||
}
|
||||
}
|
||||
return map[string]string{
|
||||
"authorization": token.Type() + " " + token.AccessToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RequireTransportSecurity indicates whether the credentials requires transport security.
|
||||
// For this implementation, we disable it if the INSECURE_DEV environment variable is set.
|
||||
func (ts TokenSource) RequireTransportSecurity() bool {
|
||||
return !env.InsecureDev
|
||||
}
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"slices"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
@ -152,7 +153,25 @@ func (rs CombinedRoutine) Stop(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// LIFOStopRoutine is a list of routines which are started in unison, but stopped
|
||||
// sequentially last-in-first-out (the last Routine is stopped, and once it
|
||||
// sequentially first-in-first-out order (the first Routine is stopped, and once
|
||||
// it successfully stops, the next routine is stopped).
|
||||
//
|
||||
// This is useful for services where subprocessors should be stopped before the
|
||||
// primary service stops for a graceful shutdown.
|
||||
type FIFOSTopRoutine []Routine
|
||||
|
||||
func (r FIFOSTopRoutine) Name() string { return "fifo" }
|
||||
|
||||
func (r FIFOSTopRoutine) Start() { CombinedRoutine(r).Start() }
|
||||
|
||||
func (r FIFOSTopRoutine) Stop(ctx context.Context) error {
|
||||
// Pass self inverted into LIFOStopRoutine
|
||||
slices.Reverse(r)
|
||||
return LIFOStopRoutine(r).Stop(ctx)
|
||||
}
|
||||
|
||||
// LIFOStopRoutine is a list of routines which are started in unison, but stopped
|
||||
// sequentially last-in-first-out order (the last Routine is stopped, and once it
|
||||
// successfully stops, the next routine is stopped).
|
||||
//
|
||||
// This is useful for services where subprocessors should be stopped before the
|
||||
|
||||
@ -166,3 +166,29 @@ func TestLIFOStopRoutine(t *testing.T) {
|
||||
// stops in reverse
|
||||
assert.Equal(t, []string{"r3", "r2", "r1"}, stopped)
|
||||
}
|
||||
|
||||
func TestFIFOStopRoutine(t *testing.T) {
|
||||
// use an unguarded slice because FIFOSTopRoutine should only stop in sequence
|
||||
var stopped []string
|
||||
r1 := NewMockRoutine()
|
||||
r1.StopFunc.PushHook(func(context.Context) error {
|
||||
stopped = append(stopped, "r1")
|
||||
return nil
|
||||
})
|
||||
r2 := NewMockRoutine()
|
||||
r2.StopFunc.PushHook(func(context.Context) error {
|
||||
stopped = append(stopped, "r2")
|
||||
return nil
|
||||
})
|
||||
r3 := NewMockRoutine()
|
||||
r3.StopFunc.PushHook(func(context.Context) error {
|
||||
stopped = append(stopped, "r3")
|
||||
return nil
|
||||
})
|
||||
|
||||
r := FIFOSTopRoutine{r1, r2, r3}
|
||||
err := r.Stop(context.Background())
|
||||
require.NoError(t, err)
|
||||
// stops in order
|
||||
assert.Equal(t, []string{"r1", "r2", "r3"}, stopped)
|
||||
}
|
||||
|
||||
8
lib/enterpriseportal/BUILD.bazel
Normal file
8
lib/enterpriseportal/BUILD.bazel
Normal file
@ -0,0 +1,8 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "enterpriseportal",
|
||||
srcs = ["enterpriseportal.go"],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/lib/enterpriseportal",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@ -12,4 +12,5 @@ sg gen buf \
|
||||
lib/enterpriseportal/codyaccess/v1/buf.gen.yaml
|
||||
```
|
||||
|
||||
> **EVERYTHING HERE IS IN A DRAFT STATE** - see [RFC 885](https://docs.google.com/document/d/1tiaW1IVKm_YSSYhH-z7Q8sv4HSO_YJ_Uu6eYDjX7uU4/edit#heading=h.tdaxc5h34u7q).
|
||||
> [!CAUTION]
|
||||
> These APIs have **production dependents**. Make changes with extreme care for backwards-compatibility.
|
||||
|
||||
1
lib/enterpriseportal/enterpriseportal.go
Normal file
1
lib/enterpriseportal/enterpriseportal.go
Normal file
@ -0,0 +1 @@
|
||||
package enterpriseportal
|
||||
@ -432,3 +432,10 @@
|
||||
package: main
|
||||
interfaces:
|
||||
- BigQueryWriter
|
||||
- filename: cmd/cody-gateway/internal/actor/productsubscription/productsubscriptiontest/mocks.go
|
||||
package: productsubscriptiontest
|
||||
sources:
|
||||
- path: github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/actor/productsubscription
|
||||
interfaces:
|
||||
- EnterprisePortalClient
|
||||
|
||||
|
||||
@ -317,6 +317,17 @@ commands:
|
||||
CODY_GATEWAY_FIREWORKS_ACCESS_TOKEN: sekret
|
||||
CODY_GATEWAY_SOURCEGRAPH_EMBEDDINGS_API_TOKEN: sekret
|
||||
CODY_GATEWAY_GOOGLE_ACCESS_TOKEN: sekret
|
||||
# Connect to services that require SAMS M2M http://go/sams-m2m
|
||||
SAMS_URL: https://accounts.sgdev.org
|
||||
# Connect to Enterprise Portal running locally
|
||||
CODY_GATEWAY_ENTERPRISE_PORTAL_URL: http://localhost:6081
|
||||
externalSecrets:
|
||||
SAMS_CLIENT_ID:
|
||||
project: sourcegraph-local-dev
|
||||
name: SG_LOCAL_DEV_SAMS_CLIENT_ID
|
||||
SAMS_CLIENT_SECRET:
|
||||
project: sourcegraph-local-dev
|
||||
name: SG_LOCAL_DEV_SAMS_CLIENT_SECRET
|
||||
watch:
|
||||
- lib
|
||||
- internal
|
||||
@ -422,7 +433,6 @@ commands:
|
||||
ENTERPRISE_PORTAL_SAMS_CLIENT_SECRET:
|
||||
project: sourcegraph-local-dev
|
||||
name: SG_LOCAL_DEV_SAMS_CLIENT_SECRET
|
||||
|
||||
watch:
|
||||
- lib
|
||||
- cmd/enterprise-portal
|
||||
@ -1052,6 +1062,18 @@ bazelCommands:
|
||||
CODY_GATEWAY_FIREWORKS_ACCESS_TOKEN: sekret
|
||||
CODY_GATEWAY_SOURCEGRAPH_EMBEDDINGS_API_TOKEN: sekret
|
||||
CODY_GATEWAY_GOOGLE_ACCESS_TOKEN: sekret
|
||||
# Connect to services that require SAMS M2M http://go/sams-m2m
|
||||
SAMS_URL: https://accounts.sgdev.org
|
||||
# Connect to Enterprise Portal running locally
|
||||
CODY_GATEWAY_ENTERPRISE_PORTAL_URL: http://localhost:6081
|
||||
externalSecrets:
|
||||
SAMS_CLIENT_ID:
|
||||
project: sourcegraph-local-dev
|
||||
name: SG_LOCAL_DEV_SAMS_CLIENT_ID
|
||||
SAMS_CLIENT_SECRET:
|
||||
project: sourcegraph-local-dev
|
||||
name: SG_LOCAL_DEV_SAMS_CLIENT_SECRET
|
||||
|
||||
docsite:
|
||||
runTarget: //doc:serve
|
||||
searcher:
|
||||
@ -1394,6 +1416,7 @@ commandsets:
|
||||
- blobstore
|
||||
- embeddings
|
||||
- cody-gateway
|
||||
- enterprise-portal # required by Cody Gateway
|
||||
env:
|
||||
SOURCEGRAPHDOTCOM_MODE: true
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user