feat/sg: add 'sg enterprise' commands for Cody Analytics (#63414)

Closes CORE-194 - added a bit more than strictly needed here, but this
PR adds:

- `sg enterprise subscription list`
- `sg enterprise subscription set-instance-domain`
- `sg enterprise update-membership`
- `sg enterprise license list`

## Test plan

<img width="1055" alt="image"
src="https://github.com/sourcegraph/sourcegraph/assets/23356519/48ec40b0-fbac-4513-9ad8-fc3174774ada">


![image](https://github.com/sourcegraph/sourcegraph/assets/23356519/806fd054-806b-4ecb-a969-32900112f368)
This commit is contained in:
Robert Lin 2024-06-21 16:29:31 -07:00 committed by GitHub
parent 5770f30389
commit cb3a1e4dc8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 819 additions and 338 deletions

483
deps.bzl

File diff suppressed because it is too large Load Diff

View File

@ -54,6 +54,7 @@ go_library(
"//dev/sg/buf",
"//dev/sg/ci",
"//dev/sg/dependencies",
"//dev/sg/enterprise",
"//dev/sg/internal/analytics",
"//dev/sg/internal/background",
"//dev/sg/internal/backport",

View File

@ -0,0 +1,25 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "enterprise",
srcs = ["sg_enterprise.go"],
importpath = "github.com/sourcegraph/sourcegraph/dev/sg/enterprise",
visibility = ["//visibility:public"],
deps = [
"//dev/sg/internal/category",
"//dev/sg/internal/std",
"//dev/sg/sams/samsflags",
"//lib/enterpriseportal/subscriptions/v1:subscriptions",
"//lib/enterpriseportal/subscriptions/v1/v1connect",
"//lib/errors",
"//lib/pointers",
"@com_connectrpc_connect//:connect",
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//:sourcegraph-accounts-sdk-go",
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//scopes",
"@com_github_urfave_cli_v2//:cli",
"@org_golang_google_protobuf//encoding/protojson",
"@org_golang_google_protobuf//types/known/fieldmaskpb",
"@org_golang_x_exp//maps",
"@org_golang_x_oauth2//:oauth2",
],
)

View File

@ -0,0 +1,393 @@
// Package enterprise exports 'sg enterprise' commands for interacting with the
// Sourcegraph Enterprise Portal service.
package enterprise
import (
"context"
"fmt"
"slices"
"strings"
"connectrpc.com/connect"
"github.com/urfave/cli/v2"
"golang.org/x/exp/maps"
"golang.org/x/oauth2"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/fieldmaskpb"
sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
"github.com/sourcegraph/sourcegraph-accounts-sdk-go/scopes"
subscriptionsv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/subscriptions/v1"
subscriptionsv1connect "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/subscriptions/v1/v1connect"
"github.com/sourcegraph/sourcegraph/lib/errors"
"github.com/sourcegraph/sourcegraph/lib/pointers"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/category"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/std"
"github.com/sourcegraph/sourcegraph/dev/sg/sams/samsflags"
)
const (
enterprisePortalProdURL = "https://enterprise-portal.sourcegraph.com"
enterprisePortalDevURL = "https://enterprise-portal.sgdev.org"
)
var (
scopeWriteSubscriptions = scopes.ToScope(scopes.ServiceEnterprisePortal, "subscription", scopes.ActionWrite)
scopeReadSubscriptions = scopes.ToScope(scopes.ServiceEnterprisePortal, "subscription", scopes.ActionRead)
scopeWriteSubscriptionsPermissions = scopes.ToScope(scopes.ServiceEnterprisePortal, "permission.subscription", scopes.ActionWrite)
)
var clientFlags = append(samsflags.ClientCredentials(), &cli.StringFlag{
Name: "enterprise-portal-server",
Usage: "The URL of the Enterprise Portal server to use (defaults to the appropriate one for SG_SAMS_SERVER_URL)",
})
func newSubscriptionsClient(c *cli.Context, ss ...scopes.Scope) subscriptionsv1connect.SubscriptionsServiceClient {
ctx := c.Context
samsServer := c.String("sams-server")
enterprisePortal := enterprisePortalDevURL
if epServer := c.String("enterprise-portal-server"); epServer != "" {
enterprisePortal = epServer
} else if samsServer == samsflags.SAMSProdURL {
enterprisePortal = enterprisePortalProdURL
}
std.Out.WriteSuggestionf("Using %q and %q",
enterprisePortal, samsServer)
return subscriptionsv1connect.NewSubscriptionsServiceClient(
oauth2.NewClient(ctx, samsflags.NewClientCredentialsFromFlags(c, ss).TokenSource(ctx)),
enterprisePortal)
}
// resolveUserReference converts a 'user reference' provided as an argument or
// flag, into a SAMS account ID. The 'user reference' can either be a SAMS user
// ID, or an email address (determined by the presence of '@' which is also an
// illegal character in a SAMS user ID).
//
// Required scope: profile
func resolveUserReference(ctx context.Context, users *sams.UsersServiceV1, userReference string) (samsAccountID string, _ error) {
if strings.Contains(userReference, "@") {
user, err := users.GetUserByEmail(ctx, userReference)
if err != nil {
return "", errors.Wrapf(err, "get user by email %q", userReference)
}
samsAccountID = user.Id
} else {
user, err := users.GetUserByID(ctx, userReference) // check if it's a valid user ID
if err != nil {
return "", errors.Wrapf(err, "get user by ID %q", userReference)
}
samsAccountID = user.GetId()
}
if samsAccountID != userReference {
std.Out.WriteSuggestionf("Resolved user %q to %q", userReference, samsAccountID)
}
return samsAccountID, nil
}
// Command is the 'sg sams' toolchain for the Sourcegraph Accounts Management System (SAMS).
var Command = &cli.Command{
Name: "enterprise",
Category: category.Company,
Usage: "Manage Sourcegraph Enterprise subscriptions in Enterprise Portal",
Description: `TODO
Please reach out to #discuss-core-services for assistance if you have any questions!`,
Subcommands: []*cli.Command{{
Name: "subscription",
Usage: "Manage Sourcegraph Enterprise subscriptions in Enterprise Portal",
Subcommands: []*cli.Command{{
Name: "list",
Usage: "List subscriptions",
ArgsUsage: "[subscription IDs...]",
Flags: append(clientFlags,
&cli.StringFlag{
Name: "member.cody-analytics-viewer",
Usage: "Member with Cody Analytics viewer permission to filter for (email or SAMS user ID)",
}),
Action: func(c *cli.Context) error {
client := newSubscriptionsClient(c, scopeReadSubscriptions)
req := &subscriptionsv1.ListEnterpriseSubscriptionsRequest{
Filters: []*subscriptionsv1.ListEnterpriseSubscriptionsFilter{{
Filter: &subscriptionsv1.ListEnterpriseSubscriptionsFilter_IsArchived{
IsArchived: false,
},
}},
}
for _, subscription := range c.Args().Slice() {
if !strings.HasPrefix(subscription, subscriptionsv1.EnterpriseSubscriptionIDPrefix) {
return errors.Newf("invalid subscription ID %q, expected prefix %q",
subscription, subscriptionsv1.EnterpriseSubscriptionIDPrefix)
}
req.Filters = append(req.Filters, &subscriptionsv1.ListEnterpriseSubscriptionsFilter{
Filter: &subscriptionsv1.ListEnterpriseSubscriptionsFilter_SubscriptionId{
SubscriptionId: subscription,
},
})
}
if member := c.String("member.cody-analytics-viewer"); member != "" {
samsClient, err := samsflags.NewClientFromFlags(c, scopes.Scopes{scopes.Profile})
if err != nil {
return errors.Wrap(err, "get SAMS client")
}
samsUserID, err := resolveUserReference(c.Context, samsClient.Users(), member)
if err != nil {
return errors.Wrap(err, "resolve SAMS user ID")
}
req.Filters = append(req.Filters, &subscriptionsv1.ListEnterpriseSubscriptionsFilter{
Filter: &subscriptionsv1.ListEnterpriseSubscriptionsFilter_Permission{
Permission: &subscriptionsv1.Permission{
Type: subscriptionsv1.PermissionType_PERMISSION_TYPE_SUBSCRIPTION_CODY_ANALYTICS,
Relation: subscriptionsv1.PermissionRelation_PERMISSION_RELATION_VIEW,
SamsAccountId: samsUserID,
},
},
})
}
resp, err := client.ListEnterpriseSubscriptions(c.Context, connect.NewRequest(req))
if err != nil {
return errors.Wrap(err, "list subscriptions")
}
for _, s := range resp.Msg.GetSubscriptions() {
data, err := protojson.MarshalOptions{
Multiline: true,
}.Marshal(s)
if err != nil {
std.Out.WriteWarningf("Failed to marshal subscription %q: %s",
s.GetId(), err.Error())
continue
}
_ = std.Out.WriteCode("json", string(data))
}
std.Out.WriteSuccessf("Found %d subscriptions", len(resp.Msg.GetSubscriptions()))
return nil
},
}, {
Name: "license",
Usage: "Manage Enterprise subscription licenses",
Subcommands: []*cli.Command{{
Name: "list",
Usage: "List licenses for all or specified subscriptions",
ArgsUsage: "[subscription IDs...]",
Flags: clientFlags,
Action: func(c *cli.Context) error {
client := newSubscriptionsClient(c, scopeReadSubscriptions)
req := &subscriptionsv1.ListEnterpriseSubscriptionLicensesRequest{
Filters: []*subscriptionsv1.ListEnterpriseSubscriptionLicensesFilter{{
Filter: &subscriptionsv1.ListEnterpriseSubscriptionLicensesFilter_IsRevoked{
IsRevoked: false,
},
}},
}
for _, subscription := range c.Args().Slice() {
if !strings.HasPrefix(subscription, subscriptionsv1.EnterpriseSubscriptionIDPrefix) {
continue
}
req.Filters = append(req.Filters, &subscriptionsv1.ListEnterpriseSubscriptionLicensesFilter{
Filter: &subscriptionsv1.ListEnterpriseSubscriptionLicensesFilter_SubscriptionId{
SubscriptionId: subscription,
},
})
}
resp, err := client.ListEnterpriseSubscriptionLicenses(c.Context, connect.NewRequest(req))
if err != nil {
return errors.Wrap(err, "list subscriptions")
}
for _, s := range resp.Msg.GetLicenses() {
if k := s.GetKey(); k != nil {
k.LicenseKey = "<redacted>"
}
data, err := protojson.MarshalOptions{
Multiline: true,
}.Marshal(s)
if err != nil {
std.Out.WriteWarningf("Failed to marshal license %q: %s",
s.GetId(), err.Error())
continue
}
_ = std.Out.WriteCode("json", string(data))
}
std.Out.WriteSuccessf("Found %d licenses", len(resp.Msg.GetLicenses()))
return nil
},
}},
}, {
Name: "set-instance-domain",
Usage: "Assign an instance domain to a subscription",
ArgsUsage: "<subscription ID> <instance domain>",
Flags: clientFlags,
Action: func(c *cli.Context) error {
client := newSubscriptionsClient(c, scopeWriteSubscriptions)
s := &subscriptionsv1.EnterpriseSubscription{
Id: c.Args().Get(0),
InstanceDomain: c.Args().Get(1),
}
if s.Id == "" {
return errors.New("subscription ID required")
}
if !strings.HasPrefix(s.Id, subscriptionsv1.EnterpriseSubscriptionIDPrefix) {
return errors.Newf("subscription ID must start with %q", subscriptionsv1.EnterpriseSubscriptionIDPrefix)
}
if s.InstanceDomain == "" {
var res string
ok, err := std.PromptAndScan(std.Out, "No instance domain provided; the assigned domain will be removed, are you sure? (y/N) ", &res)
if err != nil {
return err
} else if !ok {
return errors.New("response is required")
}
if res != "y" {
return errors.New("aborting")
}
}
var err error
s.InstanceDomain, err = subscriptionsv1.NormalizeInstanceDomain(s.InstanceDomain)
if err != nil {
return errors.Wrap(err, "normalize instance domain")
}
std.Out.Writef("Assigning domain %q to subscription %q\n",
s.InstanceDomain, s.Id)
resp, err := client.UpdateEnterpriseSubscription(c.Context, connect.NewRequest(&subscriptionsv1.UpdateEnterpriseSubscriptionRequest{
Subscription: s,
UpdateMask: &fieldmaskpb.FieldMask{
Paths: []string{
"instance_domain",
},
},
}))
if err != nil {
return errors.Wrap(err, "update enterprise subscription")
}
updatedSub := resp.Msg.GetSubscription()
std.Out.WriteSuccessf("Updated subscription %q with instance domain %q\n",
pointers.Deref(pointers.NilIfZero(updatedSub.DisplayName), updatedSub.GetId()),
updatedSub.GetInstanceDomain())
return nil
},
}, {
Name: "update-membership",
Usage: "Update or assign membership to a subscription for one or more SAMS users",
Description: "Only one of --subscription-id or --subscription-instance-domain needs to be specified.",
ArgsUsage: "<SAMS account email or ID...>",
Flags: append(clientFlags,
&cli.StringFlag{
Name: "subscription-id",
Usage: "ID of the subscription to assign membership to",
},
&cli.StringFlag{
Name: "subscription-instance-domain",
Usage: "Assigned instance domain of the subscription to assign membership to",
},
&cli.StringSliceFlag{
Name: "role",
Usage: fmt.Sprintf("Roles to assign to the member - any of: [%s]",
strings.Join(func() []string {
values := maps.Clone(subscriptionsv1.Role_value)
delete(values, "ROLE_UNSPECIFIED")
keys := maps.Keys(values)
slices.Sort(keys)
return keys
}(), ", ")),
}),
Action: func(c *cli.Context) error {
ctx := context.Background()
client := newSubscriptionsClient(c,
scopeWriteSubscriptions,
scopeWriteSubscriptionsPermissions)
var (
members = c.Args().Slice() // can be email or user ID
roles = c.StringSlice("role")
subscriptionID = c.String("subscription-id")
subscriptionInstanceDomain = c.String("subscription-instance-domain")
)
if len(members) == 0 {
return errors.New("at least one SAMS account email or ID is required")
}
if subscriptionID == "" && subscriptionInstanceDomain == "" {
return errors.New("-subscription-id or -subscription-instance-domain required")
}
if subscriptionID != "" {
if !strings.HasPrefix(subscriptionID, subscriptionsv1.EnterpriseSubscriptionIDPrefix) {
return errors.Newf("subscription ID must start with %q", subscriptionsv1.EnterpriseSubscriptionIDPrefix)
}
}
if subscriptionInstanceDomain != "" {
var err error
subscriptionInstanceDomain, err = subscriptionsv1.NormalizeInstanceDomain(subscriptionInstanceDomain)
if err != nil {
return errors.Wrap(err, "normalize instance domain")
}
}
if len(roles) == 0 {
var res string
ok, err := std.PromptAndScan(std.Out, "No roles provided; all roles will be removed, are you sure? (y/N) ", &res)
if err != nil {
return err
} else if !ok {
return errors.New("response is required")
}
if res != "y" {
return errors.New("aborting")
}
}
pbRoles := make([]subscriptionsv1.Role, len(roles))
for i, r := range roles {
role, ok := subscriptionsv1.Role_value[r]
if !ok {
return errors.Newf("invalid role %q", r)
}
if role == 0 {
return errors.Newf("invalid role %q", r)
}
pbRoles[i] = subscriptionsv1.Role(role)
}
samsClient, err := samsflags.NewClientFromFlags(c, scopes.Scopes{scopes.Profile})
if err != nil {
return errors.Wrap(err, "get SAMS client")
}
var subscriptionDebugText string
if subscriptionID != "" {
subscriptionDebugText = fmt.Sprintf("subscription %q", subscriptionID)
} else {
subscriptionDebugText = fmt.Sprintf("subscription with instance domain %q", subscriptionInstanceDomain)
}
std.Out.WriteSuggestionf("Assigning %d users roles [%s] to %s",
len(members), strings.Join(roles, ", "), subscriptionDebugText)
for _, member := range members {
samsUserID, err := resolveUserReference(c.Context, samsClient.Users(), member)
if err != nil {
return errors.Wrap(err, "resolve SAMS user ID")
}
_, err = client.UpdateEnterpriseSubscriptionMembership(ctx, connect.NewRequest(&subscriptionsv1.UpdateEnterpriseSubscriptionMembershipRequest{
Membership: &subscriptionsv1.EnterpriseSubscriptionMembership{
SubscriptionId: subscriptionID,
InstanceDomain: subscriptionInstanceDomain,
MemberSamsAccountId: samsUserID,
MemberRoles: pbRoles,
},
}))
if err != nil {
return errors.Wrapf(err, "assign membership to user %q",
samsUserID)
}
}
std.Out.WriteSuccessf("Done!")
return nil
},
}},
}},
}

View File

@ -13,6 +13,7 @@ import (
"github.com/urfave/cli/v2"
"github.com/sourcegraph/sourcegraph/dev/sg/ci"
"github.com/sourcegraph/sourcegraph/dev/sg/enterprise"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/analytics"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/background"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/check"
@ -301,6 +302,7 @@ var sg = &cli.App{
msp.Command,
securityCommand,
sams.Command,
enterprise.Command,
// Util
analyticsCommand,

View File

@ -8,9 +8,9 @@ go_library(
deps = [
"//dev/sg/internal/category",
"//dev/sg/internal/std",
"//dev/sg/sams/samsflags",
"//lib/errors",
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//:sourcegraph-accounts-sdk-go",
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//scopes",
"@com_github_urfave_cli_v2//:cli",
"@org_golang_x_oauth2//clientcredentials",
],
)

View File

@ -0,0 +1,14 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "samsflags",
srcs = ["clientcredentials.go"],
importpath = "github.com/sourcegraph/sourcegraph/dev/sg/sams/samsflags",
visibility = ["//visibility:public"],
deps = [
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//:sourcegraph-accounts-sdk-go",
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//scopes",
"@com_github_urfave_cli_v2//:cli",
"@org_golang_x_oauth2//clientcredentials",
],
)

View File

@ -0,0 +1,61 @@
package samsflags
import (
"fmt"
"github.com/urfave/cli/v2"
"golang.org/x/oauth2/clientcredentials"
sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
"github.com/sourcegraph/sourcegraph-accounts-sdk-go/scopes"
)
const (
SAMSDevURL = "https://accounts.sgdev.org"
SAMSProdURL = "https://accounts.sourcegraph.com"
)
func ClientCredentials() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "sams-server",
Aliases: []string{"sams"},
EnvVars: []string{"SG_SAMS_SERVER_URL"},
Value: SAMSDevURL,
Usage: fmt.Sprintf("URL of the Sourcegraph Accounts Management System (SAMS) server - one of %q or %q",
SAMSProdURL, SAMSDevURL),
},
&cli.StringFlag{
Name: "client-id",
EnvVars: []string{"SG_SAMS_CLIENT_ID"},
Usage: "Client ID of the Sourcegraph Accounts Management System (SAMS) client",
Required: true,
},
&cli.StringFlag{
Name: "client-secret",
EnvVars: []string{"SG_SAMS_CLIENT_SECRET"},
Usage: "Client secret for the Sourcegraph Accounts Management System (SAMS) client",
Required: true,
},
}
}
// NewClientCredentialsFromFlags returns a new client credentials config from
// clientCredentialsFlags.
func NewClientCredentialsFromFlags(c *cli.Context, ss scopes.Scopes) *clientcredentials.Config {
return &clientcredentials.Config{
ClientID: c.String("client-id"),
ClientSecret: c.String("client-secret"),
TokenURL: c.String("sams-server") + "/oauth/token",
Scopes: scopes.ToStrings(ss),
}
}
func NewClientFromFlags(c *cli.Context, ss scopes.Scopes) (*sams.ClientV1, error) {
return sams.NewClientV1(sams.ClientV1Config{
ConnConfig: sams.ConnConfig{
ExternalURL: c.String("sams-server"),
},
TokenSource: NewClientCredentialsFromFlags(c, ss).TokenSource(c.Context),
})
}

View File

@ -5,53 +5,23 @@ import (
"encoding/json"
"github.com/urfave/cli/v2"
"golang.org/x/oauth2/clientcredentials"
sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
"github.com/sourcegraph/sourcegraph-accounts-sdk-go/scopes"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/category"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/std"
"github.com/sourcegraph/sourcegraph/lib/errors"
"github.com/sourcegraph/sourcegraph/dev/sg/sams/samsflags"
)
var clientCredentialsFlags = []cli.Flag{
&cli.StringFlag{
Name: "sams-server",
Aliases: []string{"sams"},
EnvVars: []string{"SG_SAMS_SERVER_URL"},
Value: "https://accounts.sgdev.org",
Usage: "URL of the Sourcegraph Accounts Management System (SAMS) server",
},
&cli.StringFlag{
Name: "client-id",
EnvVars: []string{"SG_SAMS_CLIENT_ID"},
Usage: "Client ID of the Sourcegraph Accounts Management System (SAMS) client",
Required: true,
},
&cli.StringFlag{
Name: "client-secret",
EnvVars: []string{"SG_SAMS_CLIENT_SECRET"},
Usage: "Client secret for the Sourcegraph Accounts Management System (SAMS) client",
Required: true,
},
var clientCredentialsFlags = append(samsflags.ClientCredentials(),
&cli.StringSliceFlag{
Name: "scopes",
Aliases: []string{"s"},
Value: cli.NewStringSlice("openid", "profile", "email"),
Usage: "OAuth scopes ('$SERVICE::$PERM::$ACTION') to request from the Sourcegraph Accounts Management System (SAMS) server",
},
}
// newClientCredentialsFromFlags returns a new client credentials config from
// clientCredentialsFlags.
func newClientCredentialsFromFlags(c *cli.Context) *clientcredentials.Config {
return &clientcredentials.Config{
ClientID: c.String("client-id"),
ClientSecret: c.String("client-secret"),
TokenURL: c.String("sams-server") + "/oauth/token",
Scopes: c.StringSlice("scopes"),
}
}
)
// Command is the 'sg sams' toolchain for the Sourcegraph Accounts Management System (SAMS).
var Command = &cli.Command{
@ -67,22 +37,16 @@ Please reach out to #discuss-core-services for assistance if you have any questi
Usage: "Generate a short-lived OAuth access token and introspect it from the Sourcegraph Accounts Management System (SAMS)",
Flags: clientCredentialsFlags,
Action: func(c *cli.Context) error {
tokenSource := newClientCredentialsFromFlags(c).
TokenSource(c.Context)
samsScopes := scopes.ToScopes(c.StringSlice("scopes"))
client, err := sams.NewClientV1(
sams.ClientV1Config{
ConnConfig: sams.ConnConfig{
ExternalURL: c.String("sams-server"),
},
TokenSource: tokenSource,
},
)
client, err := samsflags.NewClientFromFlags(c, samsScopes)
if err != nil {
return errors.Wrap(err, "create client")
}
token, err := tokenSource.Token()
token, err := samsflags.NewClientCredentialsFromFlags(c, samsScopes).
TokenSource(c.Context).
Token()
if err != nil {
return errors.Wrap(err, "generate token")
}
@ -103,7 +67,8 @@ Please reach out to #discuss-core-services for assistance if you have any questi
Usage: "Generate a short-lived OAuth access token for use as a bearer token to SAMS clients",
Flags: clientCredentialsFlags,
Action: func(c *cli.Context) error {
tokenSource := newClientCredentialsFromFlags(c).
samsScopes := scopes.ToScopes(c.StringSlice("scopes"))
tokenSource := samsflags.NewClientCredentialsFromFlags(c, samsScopes).
TokenSource(c.Context)
token, err := tokenSource.Token()
if err != nil {

31
go.mod
View File

@ -59,13 +59,13 @@ replace (
)
require (
cloud.google.com/go/bigquery v1.59.1
cloud.google.com/go/kms v1.15.7
cloud.google.com/go/monitoring v1.18.0
cloud.google.com/go/bigquery v1.60.0
cloud.google.com/go/kms v1.15.8
cloud.google.com/go/monitoring v1.18.1
cloud.google.com/go/profiler v0.4.0
cloud.google.com/go/pubsub v1.37.0
cloud.google.com/go/secretmanager v1.11.5
cloud.google.com/go/storage v1.38.0
cloud.google.com/go/pubsub v1.38.0
cloud.google.com/go/secretmanager v1.12.0
cloud.google.com/go/storage v1.40.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.45.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.21.0
github.com/Khan/genqlient v0.5.0
@ -231,8 +231,8 @@ require (
golang.org/x/time v0.5.0
golang.org/x/tools v0.22.0
gonum.org/v1/gonum v0.15.0
google.golang.org/api v0.169.0
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
google.golang.org/api v0.182.0
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect
google.golang.org/protobuf v1.34.2
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v2 v2.4.0
@ -251,7 +251,7 @@ require (
chainguard.dev/apko v0.14.0
cloud.google.com/go/artifactregistry v1.14.8
cloud.google.com/go/auth v0.5.1
connectrpc.com/connect v1.16.1
connectrpc.com/connect v1.16.2
connectrpc.com/grpcreflect v1.2.0
connectrpc.com/otelconnect v0.7.0
github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai v0.5.0
@ -308,7 +308,7 @@ require (
github.com/sourcegraph/managed-services-platform-cdktf/gen/tfe v0.0.0-20240513203650-e2b1273f1c1a
github.com/sourcegraph/notionreposync v0.0.0-20240517090426-98b2d4b017d7
github.com/sourcegraph/scip v0.4.0
github.com/sourcegraph/sourcegraph-accounts-sdk-go v0.0.0-20240531163352-fe74c17cf0d1
github.com/sourcegraph/sourcegraph-accounts-sdk-go v0.0.0-20240620234947-0d3d4d90b75e
github.com/sourcegraph/sourcegraph/lib v0.0.0-20240524140455-2589fef13ea8
github.com/sourcegraph/sourcegraph/lib/managedservicesplatform v0.0.0-00010101000000-000000000000
github.com/sourcegraph/sourcegraph/monitoring v0.0.0-00010101000000-000000000000
@ -329,10 +329,11 @@ require (
)
require (
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
cloud.google.com/go/cloudsqlconn v1.5.1 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
cloud.google.com/go/longrunning v0.5.5 // indirect
cloud.google.com/go/trace v1.10.5 // indirect
cloud.google.com/go/longrunning v0.5.6 // indirect
cloud.google.com/go/trace v1.10.6 // indirect
dario.cat/mergo v1.0.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect
@ -480,8 +481,8 @@ require (
require (
bitbucket.org/creachadair/shell v0.0.7 // indirect
cloud.google.com/go v0.112.1 // indirect
cloud.google.com/go/iam v1.1.6 // indirect
cloud.google.com/go v0.114.0 // indirect
cloud.google.com/go/iam v1.1.7 // indirect
cuelang.org/go v0.4.3
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
@ -566,7 +567,7 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08 // indirect
github.com/googleapis/gax-go/v2 v2.12.2
github.com/googleapis/gax-go/v2 v2.12.4
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/gopherjs/gopherwasm v1.1.0 // indirect
github.com/gorilla/css v1.0.0 // indirect

74
go.sum
View File

@ -25,60 +25,62 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
cloud.google.com/go v0.114.0 h1:OIPFAdfrFDFO2ve2U7r/H5SwSbBzEdrBdE7xkgwc+kY=
cloud.google.com/go v0.114.0/go.mod h1:ZV9La5YYxctro1HTPug5lXH/GefROyW8PPD4T8n9J8E=
cloud.google.com/go/artifactregistry v1.14.8 h1:icIyRzJ1Ag6EOafuDuFFJ/AdStcOFRVfSGURn27/7Pk=
cloud.google.com/go/artifactregistry v1.14.8/go.mod h1:1UlSXh6sTXYrIT4kMO21AE1IDlMFemlZuX6QS+JXW7I=
cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw=
cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/bigquery v1.59.1 h1:CpT+/njKuKT3CEmswm6IbhNu9u35zt5dO4yPDLW+nG4=
cloud.google.com/go/bigquery v1.59.1/go.mod h1:VP1UJYgevyTwsV7desjzNzDND5p6hZB+Z8gZJN1GQUc=
cloud.google.com/go/bigquery v1.60.0 h1:kA96WfgvCbkqfLnr7xI5uEfJ4h4FrnkdEb0yty0KSZo=
cloud.google.com/go/bigquery v1.60.0/go.mod h1:Clwk2OeC0ZU5G5LDg7mo+h8U7KlAa5v06z5rptKdM3g=
cloud.google.com/go/cloudsqlconn v1.5.1 h1:rMtPv66pkuk2K1ciCicjZY8Ma1DSyOYSoqwPUw/Timo=
cloud.google.com/go/cloudsqlconn v1.5.1/go.mod h1:DPWjhwD5Fhv43M0RP/+7J37xo4PByfNWCzMlKa9OBwE=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/datacatalog v1.19.3 h1:A0vKYCQdxQuV4Pi0LL9p39Vwvg4jH5yYveMv50gU5Tw=
cloud.google.com/go/datacatalog v1.19.3/go.mod h1:ra8V3UAsciBpJKQ+z9Whkxzxv7jmQg1hfODr3N3YPJ4=
cloud.google.com/go/datacatalog v1.20.0 h1:BGDsEjqpAo0Ka+b9yDLXnE5k+jU3lXGMh//NsEeDMIg=
cloud.google.com/go/datacatalog v1.20.0/go.mod h1:fSHaKjIroFpmRrYlwz9XBB2gJBpXufpnxyAKaT4w6L0=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI=
cloud.google.com/go/kms v1.15.7 h1:7caV9K3yIxvlQPAcaFffhlT7d1qpxjB1wHBtjWa13SM=
cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI=
cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA=
cloud.google.com/go/kms v1.15.8 h1:szIeDCowID8th2i8XE4uRev5PMxQFqW+JjwYxL9h6xs=
cloud.google.com/go/kms v1.15.8/go.mod h1:WoUHcDjD9pluCg7pNds131awnH429QGvRM3N/4MyoVs=
cloud.google.com/go/logging v1.9.0 h1:iEIOXFO9EmSiTjDmfpbRjOxECO7R8C7b8IXUGOj7xZw=
cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE=
cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg=
cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s=
cloud.google.com/go/monitoring v1.18.0 h1:NfkDLQDG2UR3WYZVQE8kwSbUIEyIqJUPl+aOQdFH1T4=
cloud.google.com/go/monitoring v1.18.0/go.mod h1:c92vVBCeq/OB4Ioyo+NbN2U7tlg5ZH41PZcdvfc+Lcg=
cloud.google.com/go/longrunning v0.5.6 h1:xAe8+0YaWoCKr9t1+aWe+OeQgN/iJK1fEgZSXmjuEaE=
cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA=
cloud.google.com/go/monitoring v1.18.1 h1:0yvFXK+xQd95VKo6thndjwnJMno7c7Xw1CwMByg0B+8=
cloud.google.com/go/monitoring v1.18.1/go.mod h1:52hTzJ5XOUMRm7jYi7928aEdVxBEmGwA0EjNJXIBvt8=
cloud.google.com/go/profiler v0.4.0 h1:ZeRDZbsOBDyRG0OiK0Op1/XWZ3xeLwJc9zjkzczUxyY=
cloud.google.com/go/profiler v0.4.0/go.mod h1:RvPlm4dilIr3oJtAOeFQU9Lrt5RoySHSDj4pTd6TWeU=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/pubsub v1.37.0 h1:0uEEfaB1VIJzabPpwpZf44zWAKAme3zwKKxHk7vJQxQ=
cloud.google.com/go/pubsub v1.37.0/go.mod h1:YQOQr1uiUM092EXwKs56OPT650nwnawc+8/IjoUeGzQ=
cloud.google.com/go/secretmanager v1.11.5 h1:82fpF5vBBvu9XW4qj0FU2C6qVMtj1RM/XHwKXUEAfYY=
cloud.google.com/go/secretmanager v1.11.5/go.mod h1:eAGv+DaCHkeVyQi0BeXgAHOU0RdrMeZIASKc+S7VqH4=
cloud.google.com/go/pubsub v1.38.0 h1:J1OT7h51ifATIedjqk/uBNPh+1hkvUaH4VKbz4UuAsc=
cloud.google.com/go/pubsub v1.38.0/go.mod h1:IPMJSWSus/cu57UyR01Jqa/bNOQA+XnPF6Z4dKW4fAA=
cloud.google.com/go/secretmanager v1.12.0 h1:e5pIo/QEgiFiHPVJPxM5jbtUr4O/u5h2zLHYtkFQr24=
cloud.google.com/go/secretmanager v1.12.0/go.mod h1:Y1Gne3Ag+fZ2TDTiJc8ZJCMFbi7k1rYT4Rw30GXfvlk=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg=
cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY=
cloud.google.com/go/trace v1.10.5 h1:0pr4lIKJ5XZFYD9GtxXEWr0KkVeigc3wlGpZco0X1oA=
cloud.google.com/go/trace v1.10.5/go.mod h1:9hjCV1nGBCtXbAE4YK7OqJ8pmPYSxPA0I67JwRd5s3M=
connectrpc.com/connect v1.16.1 h1:rOdrK/RTI/7TVnn3JsVxt3n028MlTRwmK5Q4heSpjis=
connectrpc.com/connect v1.16.1/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw=
cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw=
cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g=
cloud.google.com/go/trace v1.10.6 h1:XF0Ejdw0NpRfAvuZUeQe3ClAG4R/9w5JYICo7l2weaw=
cloud.google.com/go/trace v1.10.6/go.mod h1:EABXagUjxGuKcZMy4pXyz0fJpE5Ghog3jzTxcEsVJS4=
connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE=
connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc=
connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U=
connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY=
connectrpc.com/otelconnect v0.7.0 h1:ZH55ZZtcJOTKWWLy3qmL4Pam4RzRWBJFOqTPyAqCXkY=
@ -841,8 +843,8 @@ github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPg
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@ -870,8 +872,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksP
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA=
github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc=
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -1672,8 +1674,8 @@ github.com/sourcegraph/run v0.12.0 h1:3A8w5e8HIYPfafHekvmdmmh42RHKGVhmiTZAPJclg7
github.com/sourcegraph/run v0.12.0/go.mod h1:PwaP936BTnAJC1cqR5rSbG5kOs/EWStTK3lqvMX5GUA=
github.com/sourcegraph/scip v0.4.0 h1:Tqf5ThVlPu8fV+WeTkJEbW34fPOfDUpbxQWU4iLvaQI=
github.com/sourcegraph/scip v0.4.0/go.mod h1:bmBqGJCl3nJw55jt8WXXqx4+TXR5WPO80qw7KoCvXZU=
github.com/sourcegraph/sourcegraph-accounts-sdk-go v0.0.0-20240531163352-fe74c17cf0d1 h1:+7J5NMA9FJDaf0IhNpIcTEg+Gzu/GN5dRT40wdFU10I=
github.com/sourcegraph/sourcegraph-accounts-sdk-go v0.0.0-20240531163352-fe74c17cf0d1/go.mod h1:/MWl0sFvn6w26Y067CkEJgklfxx8gCzbEJ3q6cBzDro=
github.com/sourcegraph/sourcegraph-accounts-sdk-go v0.0.0-20240620234947-0d3d4d90b75e h1:m6ljyXK3Dp/5iWgfqtkKsTD8Ksyjwl0BSZgEpdgoG48=
github.com/sourcegraph/sourcegraph-accounts-sdk-go v0.0.0-20240620234947-0d3d4d90b75e/go.mod h1:0bD4781hPFlS2tTcoUERY8aSu/UTA6YQV7Iv2TJvtm8=
github.com/sourcegraph/yaml v1.0.1-0.20200714132230-56936252f152 h1:z/MpntplPaW6QW95pzcAR/72Z5TWDyDnSo0EOcyij9o=
github.com/sourcegraph/yaml v1.0.1-0.20200714132230-56936252f152/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/sourcegraph/zoekt v0.0.0-20240620084526-5ac92b1a7d4a h1:9g+6UUpAfhShhYCSPvU8YUTOexLPk45TV/dNEU6qZLw=
@ -1848,8 +1850,8 @@ github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8=
github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.bobheadxi.dev/streamline v1.3.2 h1:EwbDggkws9Qo/fl4Zo801Z8mgf4xZBOX7/bbfTthsy4=
go.bobheadxi.dev/streamline v1.3.2/go.mod h1:QSS0MlQm+3ABEr0uMYrkAGYiILwJvTIGFygVARxDFdg=
go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8=
go.einride.tech/aip v0.66.0/go.mod h1:qAhMsfT7plxBX+Oy7Huol6YUvZ0ZzdUz26yZsQwfl1M=
go.einride.tech/aip v0.67.1 h1:d/4TW92OxXBngkSOwWS2CH5rez869KpKMaN44mdxkFI=
go.einride.tech/aip v0.67.1/go.mod h1:ZGX4/zKw8dcgzdLsrvpOOGxfxI2QSk12SlP7d6c0/XI=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
@ -2409,8 +2411,8 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY=
google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg=
google.golang.org/api v0.182.0 h1:if5fPvudRQ78GeRx3RayIoiuV7modtErPIZC/T2bIvE=
google.golang.org/api v0.182.0/go.mod h1:cGhjy4caqA5yXRzEhkHI8Y9mfyC2VLTlER2l08xaqtM=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -2464,8 +2466,8 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=

View File

@ -63,3 +63,13 @@ func Slice[S []V, V any](s S) []*V {
}
return slice
}
// NilIfZero returns nil if the provided value is zero, otherwise returns pointer
// to the value.
func NilIfZero[T comparable](val T) *T {
var zero T
if val == zero {
return nil
}
return &val
}