mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 12:51:55 +00:00
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"> 
This commit is contained in:
parent
5770f30389
commit
cb3a1e4dc8
@ -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",
|
||||
|
||||
25
dev/sg/enterprise/BUILD.bazel
Normal file
25
dev/sg/enterprise/BUILD.bazel
Normal 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",
|
||||
],
|
||||
)
|
||||
393
dev/sg/enterprise/sg_enterprise.go
Normal file
393
dev/sg/enterprise/sg_enterprise.go
Normal 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
|
||||
},
|
||||
}},
|
||||
}},
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
14
dev/sg/sams/samsflags/BUILD.bazel
Normal file
14
dev/sg/sams/samsflags/BUILD.bazel
Normal 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",
|
||||
],
|
||||
)
|
||||
61
dev/sg/sams/samsflags/clientcredentials.go
Normal file
61
dev/sg/sams/samsflags/clientcredentials.go
Normal 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),
|
||||
})
|
||||
}
|
||||
@ -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
31
go.mod
@ -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
74
go.sum
@ -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=
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user