diff --git a/cli/operations/account/login.go b/cli/operations/account/login.go index fd78a5377..799758f0d 100644 --- a/cli/operations/account/login.go +++ b/cli/operations/account/login.go @@ -10,6 +10,7 @@ import ( "github.com/harness/gitness/cli/provide" "github.com/harness/gitness/cli/textui" + "github.com/harness/gitness/internal/api/controller/user" "gopkg.in/alecthomas/kingpin.v2" ) @@ -21,19 +22,25 @@ type loginCommand struct { func (c *loginCommand) run(*kingpin.ParseContext) error { ss := provide.NewSession() - username, password := textui.Credentials() + loginIdentifier, password := textui.Credentials() ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - ts, err := provide.OpenClient(c.server).Login(ctx, username, password) + in := &user.LoginInput{ + LoginIdentifier: loginIdentifier, + Password: password, + } + + ts, err := provide.OpenClient(c.server).Login(ctx, in) if err != nil { return err } return ss. SetURI(c.server). - SetExpiresAt(ts.Token.ExpiresAt). + // login token always has an expiry date + SetExpiresAt(*ts.Token.ExpiresAt). SetAccessToken(ts.AccessToken). Store() } diff --git a/cli/operations/account/register.go b/cli/operations/account/register.go index e9941ee71..e3343abb4 100644 --- a/cli/operations/account/register.go +++ b/cli/operations/account/register.go @@ -11,6 +11,7 @@ import ( "github.com/harness/gitness/cli/provide" "github.com/harness/gitness/cli/session" "github.com/harness/gitness/cli/textui" + "github.com/harness/gitness/internal/api/controller/user" "gopkg.in/alecthomas/kingpin.v2" ) @@ -30,18 +31,26 @@ type registerCommand struct { func (c *registerCommand) run(*kingpin.ParseContext) error { ss := provide.NewSession() - username, name, email, password := textui.Registration() + uid, displayName, email, password := textui.Registration() ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - ts, err := provide.OpenClient(c.server).Register(ctx, username, name, email, password) + input := &user.RegisterInput{ + UID: uid, + Email: email, + DisplayName: displayName, + Password: password, + } + + ts, err := provide.OpenClient(c.server).Register(ctx, input) if err != nil { return err } return ss. SetURI(c.server). - SetExpiresAt(ts.Token.ExpiresAt). + // register token always has an expiry date + SetExpiresAt(*ts.Token.ExpiresAt). SetAccessToken(ts.AccessToken). Store() } diff --git a/cli/operations/user/create_pat.go b/cli/operations/user/create_pat.go index 94bea35e8..f9875bff2 100644 --- a/cli/operations/user/create_pat.go +++ b/cli/operations/user/create_pat.go @@ -16,6 +16,7 @@ import ( "github.com/harness/gitness/types/enum" "github.com/drone/funcmap" + "github.com/gotidy/ptr" "gopkg.in/alecthomas/kingpin.v2" ) @@ -38,9 +39,14 @@ func (c *createPATCommand) run(*kingpin.ParseContext) error { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() + var lifeTime *time.Duration + if c.lifetimeInS > 0 { + lifeTime = ptr.Duration(time.Duration(int64(time.Second) * c.lifetimeInS)) + } + in := user.CreateTokenInput{ UID: c.uid, - Lifetime: time.Duration(int64(time.Second) * c.lifetimeInS), + Lifetime: lifeTime, Grants: enum.AccessGrantAll, } @@ -71,7 +77,7 @@ func registerCreatePAT(app *kingpin.CmdClause) { Required().StringVar(&c.uid) cmd.Arg("lifetime", "the lifetime of the token in seconds"). - Required().Int64Var(&c.lifetimeInS) + Int64Var(&c.lifetimeInS) cmd.Flag("json", "json encode the output"). BoolVar(&c.json) diff --git a/cli/server/server.go b/cli/server/server.go index 3d6cd229a..7f95e8f0d 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -24,12 +24,6 @@ import ( "gopkg.in/alecthomas/kingpin.v2" ) -const ( - // GraceFullShutdownTime defines the max time we wait when shutting down a server. - // 5min should be enough for most git clones to complete. - GraceFullShutdownTime = 300 * time.Second -) - type command struct { envfile string enableGitRPC bool @@ -109,7 +103,7 @@ func (c *command) run(*kingpin.ParseContext) error { log.Info().Msg("shutting down gracefully (press Ctrl+C again to force)") // shutdown servers gracefully - shutdownCtx, cancel := context.WithTimeout(context.Background(), GraceFullShutdownTime) + shutdownCtx, cancel := context.WithTimeout(context.Background(), config.GracefulShutdownTime) defer cancel() if sErr := shutdownHTTP(shutdownCtx); sErr != nil { diff --git a/cli/textui/input.go b/cli/textui/input.go index 1aa93be8f..b6491ecbd 100644 --- a/cli/textui/input.go +++ b/cli/textui/input.go @@ -14,31 +14,41 @@ import ( "golang.org/x/term" ) -// Registration returns the username, name, email and password from stdin. +// Registration returns the userID, displayName, email and password from stdin. func Registration() (string, string, string, string) { - return Username(), Name(), Email(), Password() + return UserID(), DisplayName(), Email(), Password() } -// Credentials returns the username and password from stdin. +// Credentials returns the login identifier and password from stdin. func Credentials() (string, string) { - return Username(), Password() + return LoginIdentifier(), Password() } -// Username returns the username from stdin. -func Username() string { +// UserID returns the user ID from stdin. +func UserID() string { reader := bufio.NewReader(os.Stdin) - fmt.Print("Enter Username: ") - username, _ := reader.ReadString('\n') + fmt.Print("Enter User ID: ") + uid, _ := reader.ReadString('\n') - return strings.TrimSpace(username) + return strings.TrimSpace(uid) } -// Name returns the name from stdin. -func Name() string { +// LoginIdentifier returns the login identifier from stdin. +func LoginIdentifier() string { reader := bufio.NewReader(os.Stdin) - fmt.Print("Enter Name: ") + fmt.Print("Enter User ID or Email: ") + id, _ := reader.ReadString('\n') + + return strings.TrimSpace(id) +} + +// DisplayName returns the display name from stdin. +func DisplayName() string { + reader := bufio.NewReader(os.Stdin) + + fmt.Print("Enter Display Name: ") name, _ := reader.ReadString('\n') return strings.TrimSpace(name) diff --git a/client/client.go b/client/client.go index 2dc057a3f..6b3ec24c4 100644 --- a/client/client.go +++ b/client/client.go @@ -59,27 +59,18 @@ func (c *HTTPClient) SetDebug(debug bool) { } // Login authenticates the user and returns a JWT token. -func (c *HTTPClient) Login(ctx context.Context, username, password string) (*types.TokenResponse, error) { - form := &url.Values{} - form.Add("username", username) - form.Add("password", password) +func (c *HTTPClient) Login(ctx context.Context, input *user.LoginInput) (*types.TokenResponse, error) { out := new(types.TokenResponse) uri := fmt.Sprintf("%s/api/v1/login", c.base) - err := c.post(ctx, uri, true, form, out) + err := c.post(ctx, uri, true, input, out) return out, err } // Register registers a new user and returns a JWT token. -func (c *HTTPClient) Register(ctx context.Context, - username, displayName, email, password string) (*types.TokenResponse, error) { - form := &url.Values{} - form.Add("username", username) - form.Add("displayname", displayName) - form.Add("email", email) - form.Add("password", password) +func (c *HTTPClient) Register(ctx context.Context, input *user.RegisterInput) (*types.TokenResponse, error) { out := new(types.TokenResponse) uri := fmt.Sprintf("%s/api/v1/register", c.base) - err := c.post(ctx, uri, true, form, out) + err := c.post(ctx, uri, true, input, out) return out, err } @@ -201,12 +192,7 @@ func (c *HTTPClient) stream(ctx context.Context, rawurl, method string, noToken var buf io.ReadWriter if in != nil { buf = &bytes.Buffer{} - // if posting form data, encode the form values. - if form, ok := in.(*url.Values); ok { - if _, err = io.WriteString(buf, form.Encode()); err != nil { - log.Err(err).Msg("in stream method") - } - } else if err = json.NewEncoder(buf).Encode(in); err != nil { + if err = json.NewEncoder(buf).Encode(in); err != nil { return nil, err } } diff --git a/client/interface.go b/client/interface.go index 726ea185b..9c0b97002 100644 --- a/client/interface.go +++ b/client/interface.go @@ -14,10 +14,10 @@ import ( // Client to access the remote APIs. type Client interface { // Login authenticates the user and returns a JWT token. - Login(ctx context.Context, username, password string) (*types.TokenResponse, error) + Login(ctx context.Context, input *user.LoginInput) (*types.TokenResponse, error) // Register registers a new user and returns a JWT token. - Register(ctx context.Context, username, name, email, password string) (*types.TokenResponse, error) + Register(ctx context.Context, input *user.RegisterInput) (*types.TokenResponse, error) // Self returns the currently authenticated user. Self(ctx context.Context) (*types.User, error) diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index 96c124698..d1911d729 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -49,16 +49,24 @@ import ( func initSystem(ctx context.Context, config *types.Config) (*server.System, error) { principalUID := check.ProvidePrincipalUIDCheck() - authorizer := authz.ProvideAuthorizer() databaseConfig := server.ProvideDatabaseConfig(config) db, err := database.ProvideDatabase(ctx, databaseConfig) if err != nil { return nil, err } + pathTransformation := store.ProvidePathTransformation() + pathStore := database.ProvidePathStore(db, pathTransformation) + pathCache := cache.ProvidePathCache(pathStore, pathTransformation) + spaceStore := database.ProvideSpaceStore(db, pathCache) + principalInfoView := database.ProvidePrincipalInfoView(db) + principalInfoCache := cache.ProvidePrincipalInfoCache(principalInfoView) + membershipStore := database.ProvideMembershipStore(db, principalInfoCache) + permissionCache := authz.ProvidePermissionCache(spaceStore, membershipStore) + authorizer := authz.ProvideAuthorizer(permissionCache) principalUIDTransformation := store.ProvidePrincipalUIDTransformation() principalStore := database.ProvidePrincipalStore(db, principalUIDTransformation) tokenStore := database.ProvideTokenStore(db) - controller := user.NewController(principalUID, authorizer, principalStore, tokenStore, config) + controller := user.NewController(principalUID, authorizer, principalStore, tokenStore, membershipStore) serviceController := service.NewController(principalUID, authorizer, principalStore) bootstrapBootstrap := bootstrap.ProvideBootstrap(config, controller, serviceController) authenticator := authn.ProvideAuthenticator(principalStore, tokenStore) @@ -67,11 +75,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro return nil, err } pathUID := check.ProvidePathUIDCheck() - pathTransformation := store.ProvidePathTransformation() - pathStore := database.ProvidePathStore(db, pathTransformation) - pathCache := cache.ProvidePathCache(pathStore, pathTransformation) repoStore := database.ProvideRepoStore(db, pathCache) - spaceStore := database.ProvideSpaceStore(db, pathCache) gitrpcConfig, err := server.ProvideGitRPCClientConfig() if err != nil { return nil, err @@ -81,9 +85,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro return nil, err } repoController := repo.ProvideController(config, db, provider, pathUID, authorizer, pathStore, repoStore, spaceStore, principalStore, gitrpcInterface) - spaceController := space.ProvideController(db, provider, pathUID, authorizer, pathStore, spaceStore, repoStore, principalStore, repoController) - principalInfoView := database.ProvidePrincipalInfoView(db) - principalInfoCache := cache.ProvidePrincipalInfoCache(principalInfoView) + spaceController := space.ProvideController(db, provider, pathUID, authorizer, pathStore, spaceStore, repoStore, principalStore, repoController, membershipStore) pullReqStore := database.ProvidePullReqStore(db, principalInfoCache) pullReqActivityStore := database.ProvidePullReqActivityStore(db, principalInfoCache) codeCommentView := database.ProvideCodeCommentView(db) @@ -135,7 +137,8 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro githookController := githook.ProvideController(db, authorizer, principalStore, repoStore, eventsReporter) serviceaccountController := serviceaccount.NewController(principalUID, authorizer, principalStore, spaceStore, repoStore, tokenStore) principalController := principal.ProvideController(principalStore) - checkController := check2.ProvideController(db, authorizer, repoStore, gitrpcInterface) + checkStore := database.ProvideCheckStore(db, principalInfoCache) + checkController := check2.ProvideController(db, authorizer, repoStore, checkStore, gitrpcInterface) systemController := system.NewController(principalStore, config) apiHandler := router.ProvideAPIHandler(config, authenticator, repoController, spaceController, pullreqController, webhookController, githookController, serviceaccountController, controller, principalController, checkController, systemController) gitHandler := router.ProvideGitHandler(config, provider, repoStore, authenticator, authorizer, gitrpcInterface) diff --git a/gitrpc/client.go b/gitrpc/client.go index 1c967f131..e496e53f3 100644 --- a/gitrpc/client.go +++ b/gitrpc/client.go @@ -6,10 +6,12 @@ package gitrpc import ( "fmt" + "time" "github.com/harness/gitness/gitrpc/rpc" "google.golang.org/grpc" + "google.golang.org/grpc/backoff" "google.golang.org/grpc/credentials/insecure" ) @@ -34,6 +36,7 @@ func New(config Config) (*Client, error) { // preparate all grpc options grpcOpts := []grpc.DialOption{ + grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingPolicy":"%s"}`, config.LoadBalancingPolicy)), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithChainUnaryInterceptor( logIntc.UnaryClientInterceptor(), @@ -41,6 +44,18 @@ func New(config Config) (*Client, error) { grpc.WithChainStreamInterceptor( logIntc.StreamClientInterceptor(), ), + grpc.WithConnectParams( + grpc.ConnectParams{ + // This config optimizes for connection recovery instead of load reduction. + // NOTE: we only expect limited number of internal clients, thus low number of connections. + Backoff: backoff.Config{ + BaseDelay: 100 * time.Millisecond, + Multiplier: 1.6, // same as default + Jitter: 0.2, // same as default + MaxDelay: time.Second, + }, + }, + ), } conn, err := grpc.Dial(config.Addr, grpcOpts...) diff --git a/gitrpc/config.go b/gitrpc/config.go index fe95deaa1..95141a6bb 100644 --- a/gitrpc/config.go +++ b/gitrpc/config.go @@ -10,7 +10,8 @@ import ( // Config represents the config for the gitrpc client. type Config struct { - Addr string `envconfig:"GITRPC_CLIENT_ADDR" default:"127.0.0.1:3001"` + Addr string `envconfig:"GITRPC_CLIENT_ADDR" default:"127.0.0.1:3001"` + LoadBalancingPolicy string `envconfig:"GITRPC_CLIENT_LOAD_BALANCING_POLICY" default:"pick_first"` } func (c *Config) Validate() error { diff --git a/gitrpc/interface.go b/gitrpc/interface.go index b4ee14416..2a4941407 100644 --- a/gitrpc/interface.go +++ b/gitrpc/interface.go @@ -17,7 +17,7 @@ type Interface interface { GetSubmodule(ctx context.Context, params *GetSubmoduleParams) (*GetSubmoduleOutput, error) GetBlob(ctx context.Context, params *GetBlobParams) (*GetBlobOutput, error) CreateBranch(ctx context.Context, params *CreateBranchParams) (*CreateBranchOutput, error) - CreateTag(ctx context.Context, params *CreateTagParams) (*CreateTagOutput, error) + CreateCommitTag(ctx context.Context, params *CreateCommitTagParams) (*CreateCommitTagOutput, error) DeleteTag(ctx context.Context, params *DeleteTagParams) error GetBranch(ctx context.Context, params *GetBranchParams) (*GetBranchOutput, error) DeleteBranch(ctx context.Context, params *DeleteBranchParams) error diff --git a/gitrpc/internal/gitea/tag.go b/gitrpc/internal/gitea/tag.go index 41e871bef..55817f8c8 100644 --- a/gitrpc/internal/gitea/tag.go +++ b/gitrpc/internal/gitea/tag.go @@ -39,16 +39,38 @@ func (g Adapter) GetAnnotatedTags(ctx context.Context, repoPath string, shas []s return giteaGetAnnotatedTags(ctx, repoPath, shas) } -func (g Adapter) CreateAnnotatedTag( +// CreateTag creates the tag pointing at the provided SHA (could be any type, e.g. commit, tag, blob, ...) +func (g Adapter) CreateTag( ctx context.Context, repoPath string, - request *types.CreateTagRequest, + name string, + targetSHA string, + opts *types.CreateTagOptions, ) error { - cmd := gitea.NewCommand(ctx, "tag", "-a", "-m", request.Message, "--", request.Name, request.TargetSha) - env := []string{ - "GIT_COMMITTER_NAME=" + request.TaggerName, - "GIT_COMMITTER_EMAIL=" + request.TaggerEmail, + args := []string{ + "tag", } + env := []string{} + + if opts != nil && opts.Message != "" { + args = append(args, + "-m", + opts.Message, + ) + env = append(env, + "GIT_COMMITTER_NAME="+opts.Tagger.Identity.Name, + "GIT_COMMITTER_EMAIL="+opts.Tagger.Identity.Email, + "GIT_COMMITTER_DATE="+opts.Tagger.When.Format(time.RFC3339), + ) + } + + args = append(args, + "--", + name, + targetSHA, + ) + + cmd := gitea.NewCommand(ctx, args...) _, _, err := cmd.RunStdString(&gitea.RunOpts{Dir: repoPath, Env: env}) if err != nil { return processGiteaErrorf(err, "Service failed to create a tag") diff --git a/gitrpc/internal/service/branch.go b/gitrpc/internal/service/branch.go index f4ef213dd..91da500ba 100644 --- a/gitrpc/internal/service/branch.go +++ b/gitrpc/internal/service/branch.go @@ -62,7 +62,7 @@ func (s ReferenceService) CreateBranch(ctx context.Context, } // get target commit (as target could be branch/tag/commit, and tag can't be pushed using source:destination syntax) - targetCommit, err := sharedRepo.GetCommit(strings.TrimSpace(request.GetTarget())) + targetCommit, err := s.adapter.GetCommit(ctx, sharedRepo.tmpPath, strings.TrimSpace(request.GetTarget())) if git.IsErrNotExist(err) { return nil, ErrNotFoundf("target '%s' doesn't exist", request.GetTarget()) } @@ -71,7 +71,7 @@ func (s ReferenceService) CreateBranch(ctx context.Context, } // push to new branch (all changes should go through push flow for hooks and other safety meassures) - err = sharedRepo.PushCommitToBranch(ctx, base, targetCommit.ID.String(), request.GetBranchName()) + err = sharedRepo.PushCommitToBranch(ctx, base, targetCommit.SHA, request.GetBranchName()) if err != nil { return nil, processGitErrorf(err, "failed to push new branch '%s'", request.GetBranchName()) } diff --git a/gitrpc/internal/service/interface.go b/gitrpc/internal/service/interface.go index d790f5b8c..fb1bd445e 100644 --- a/gitrpc/internal/service/interface.go +++ b/gitrpc/internal/service/interface.go @@ -41,7 +41,7 @@ type GitAdapter interface { GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) GetAnnotatedTag(ctx context.Context, repoPath string, sha string) (*types.Tag, error) GetAnnotatedTags(ctx context.Context, repoPath string, shas []string) ([]types.Tag, error) - CreateAnnotatedTag(ctx context.Context, repoPath string, request *types.CreateTagRequest) error + CreateTag(ctx context.Context, repoPath string, name string, targetSHA string, opts *types.CreateTagOptions) error GetBranch(ctx context.Context, repoPath string, branchName string) (*types.Branch, error) GetCommitDivergences(ctx context.Context, repoPath string, requests []types.CommitDivergenceRequest, max int32) ([]types.CommitDivergence, error) diff --git a/gitrpc/internal/service/mapping.go b/gitrpc/internal/service/mapping.go index 8d6c81c1c..e00078465 100644 --- a/gitrpc/internal/service/mapping.go +++ b/gitrpc/internal/service/mapping.go @@ -156,10 +156,11 @@ func mapRenameDetails(renameDetails []types.PathRenameDetails) []*rpc.RenameDeta return renameDetailsList } -func mapCommitTag(tag *types.Tag) *rpc.CommitTag { +func mapAnnotatedTag(tag *types.Tag) *rpc.CommitTag { return &rpc.CommitTag{ Name: tag.Name, Sha: tag.Sha, + Title: tag.Title, Message: tag.Message, Tagger: mapGitSignature(tag.Tagger), IsAnnotated: true, diff --git a/gitrpc/internal/service/repo.go b/gitrpc/internal/service/repo.go index 7d49b9018..e11c6a266 100644 --- a/gitrpc/internal/service/repo.go +++ b/gitrpc/internal/service/repo.go @@ -440,7 +440,7 @@ func (s RepositoryService) HashRepository( res, err := hasher.Hash(source) if err != nil { - return nil, ErrInternalf("failed to hash repo refs", err) + return nil, processGitErrorf(err, "failed to hash repository") } return &rpc.HashRepositoryResponse{ diff --git a/gitrpc/internal/service/tag.go b/gitrpc/internal/service/tag.go index 21108ce8a..d12517460 100644 --- a/gitrpc/internal/service/tag.go +++ b/gitrpc/internal/service/tag.go @@ -6,7 +6,10 @@ package service import ( "context" + "errors" "fmt" + "strings" + "time" "github.com/harness/gitness/gitrpc/internal/types" "github.com/harness/gitness/gitrpc/rpc" @@ -46,31 +49,53 @@ func (s ReferenceService) ListCommitTags(request *rpc.ListCommitTagsRequest, } } + // populate annotation data for all annotated tags if len(annotatedTagSHAs) > 0 { - var gitTags []types.Tag - gitTags, err = s.adapter.GetAnnotatedTags(ctx, repoPath, annotatedTagSHAs) + var aTags []types.Tag + aTags, err = s.adapter.GetAnnotatedTags(ctx, repoPath, annotatedTagSHAs) if err != nil { return processGitErrorf(err, "failed to get annotated tag") } - ai := 0 // since only some tags are annotated, we need second index - for i := range tags { - if !tags[i].IsAnnotated { + ai := 0 // index for annotated tags + ri := 0 // read index for all tags + wi := 0 // write index for all tags (as we might remove some non-commit tags) + for ; ri < len(tags); ri++ { + // always copy the current read element to the latest write position (doesn't mean it's kept) + tags[wi] = tags[ri] + commitSHAs[wi] = commitSHAs[ri] + + // keep the tag as is if it's not annotated + if !tags[ri].IsAnnotated { + wi++ + continue + } + + // filter out annotated tags that don't point to commit objects (blobs, trees, nested tags, ...) + // we don't actually wanna write it, so keep write index + // TODO: Support proper pagination: https://harness.atlassian.net/browse/CODE-669 + if aTags[ai].TargetType != types.GitObjectTypeCommit { + ai++ continue } // correct the commitSHA for the annotated tag (currently it is the tag sha, not the commit sha) // NOTE: This is required as otherwise gitea will wrongly set the committer to the tagger signature. - commitSHAs[i] = gitTags[ai].TargetSha + commitSHAs[wi] = aTags[ai].TargetSha // update tag information with annotation details // NOTE: we keep the name from the reference and ignore the annotated name (similar to github) - tags[i].Message = gitTags[ai].Message - tags[i].Title = gitTags[ai].Title - tags[i].Tagger = mapGitSignature(gitTags[ai].Tagger) + tags[wi].Message = aTags[ai].Message + tags[wi].Title = aTags[ai].Title + tags[wi].Tagger = mapGitSignature(aTags[ai].Tagger) ai++ + wi++ } + + // truncate slices based on what was removed + tags = tags[:wi] + commitSHAs = commitSHAs[:wi] } // get commits if needed (single call for perf savings: 1s-4s vs 5s-20s) @@ -185,10 +210,10 @@ func listCommitTagsWalkReferencesHandler(tags *[]*rpc.CommitTag) types.WalkRefer return nil } } -func (s ReferenceService) CreateTag( +func (s ReferenceService) CreateCommitTag( ctx context.Context, - request *rpc.CreateTagRequest, -) (*rpc.CreateTagResponse, error) { + request *rpc.CreateCommitTagRequest, +) (*rpc.CreateCommitTagResponse, error) { base := request.GetBase() if base == nil { return nil, types.ErrBaseCannotBeEmpty @@ -208,35 +233,87 @@ func (s ReferenceService) CreateTag( defer sharedRepo.Close(ctx) + // clone repo (with HEAD branch - target might be anything) err = sharedRepo.Clone(ctx, "") if err != nil { - return nil, processGitErrorf(err, "failed to clone shared repo with branch '%s'", request.GetSha()) + return nil, processGitErrorf(err, "failed to clone shared repo") } - actor := request.GetBase().GetActor() - createTagRequest := types.CreateTagRequest{ - Name: request.GetTagName(), - TargetSha: request.GetSha(), - Message: request.GetMessage(), - TaggerEmail: actor.GetEmail(), - TaggerName: actor.GetName(), - } - err = s.adapter.CreateAnnotatedTag(ctx, sharedRepo.tmpPath, &createTagRequest) + // get target commit (as target could be branch/tag/commit, and tag can't be pushed using source:destination syntax) + // NOTE: in case the target is an annotated tag, the targetCommit title and message are that of the tag, not the commit + targetCommit, err := s.adapter.GetCommit(ctx, sharedRepo.tmpPath, strings.TrimSpace(request.GetTarget())) + if git.IsErrNotExist(err) { + return nil, ErrNotFoundf("target '%s' doesn't exist", request.GetTarget()) + } if err != nil { - return nil, processGitErrorf(err, "Failed to create tag %s - %s", request.GetTagName(), err.Error()) + return nil, fmt.Errorf("failed to get commit id for target '%s': %w", request.GetTarget(), err) + } + + tagger := base.GetActor() + if request.GetTagger() != nil { + tagger = request.GetTagger() + } + taggerDate := time.Now().UTC() + if request.GetTaggerDate() != 0 { + taggerDate = time.Unix(request.GetTaggerDate(), 0) + } + + createTagRequest := &types.CreateTagOptions{ + Message: request.GetMessage(), + Tagger: types.Signature{ + Identity: types.Identity{ + Name: tagger.Name, + Email: tagger.Email, + }, + When: taggerDate, + }, + } + err = s.adapter.CreateTag( + ctx, + sharedRepo.tmpPath, + request.GetTagName(), + targetCommit.SHA, + createTagRequest) + if errors.Is(err, types.ErrAlreadyExists) { + return nil, ErrAlreadyExistsf("tag '%s' already exists", request.GetTagName()) + } + if err != nil { + return nil, processGitErrorf(err, "Failed to create tag '%s'", request.GetTagName()) } if err = sharedRepo.PushTag(ctx, base, request.GetTagName()); err != nil { - return nil, processGitErrorf(err, "Failed to push the tag %s to remote", request.GetTagName()) + return nil, processGitErrorf(err, "Failed to push the tag to remote") } - tag, err := s.adapter.GetAnnotatedTag(ctx, repoPath, request.GetTagName()) + var commitTag *rpc.CommitTag + if request.GetMessage() != "" { + tag, err := s.adapter.GetAnnotatedTag(ctx, repoPath, request.GetTagName()) + if err != nil { + return nil, fmt.Errorf("failed to read annotated tag after creation: %w", err) + } + commitTag = mapAnnotatedTag(tag) + } else { + commitTag = &rpc.CommitTag{ + Name: request.GetTagName(), + IsAnnotated: false, + Sha: targetCommit.SHA, + } + } + // gitea overwrites some commit details in case getCommit(ref) was called with ref being a tag + // To avoid this issue, let's get the commit again using the actual id of the commit + // TODO: can we do this nicer? + rawCommit, err := s.adapter.GetCommit(ctx, repoPath, targetCommit.SHA) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get the raw commit '%s' after tag creation: %w", targetCommit.SHA, err) } - commitTag := mapCommitTag(tag) - return &rpc.CreateTagResponse{Tag: commitTag}, nil + + commitTag.Commit, err = mapGitCommit(rawCommit) + if err != nil { + return nil, fmt.Errorf("failed to map target commit after tag creation: %w", err) + } + + return &rpc.CreateCommitTagResponse{Tag: commitTag}, nil } func (s ReferenceService) DeleteTag( @@ -262,7 +339,8 @@ func (s ReferenceService) DeleteTag( defer sharedRepo.Close(ctx) - err = sharedRepo.Clone(ctx, request.GetTagName()) + // clone repo (with HEAD branch - tag target might be anything) + err = sharedRepo.Clone(ctx, "") if err != nil { return nil, processGitErrorf(err, "failed to clone shared repo with tag '%s'", request.GetTagName()) } diff --git a/gitrpc/internal/types/types.go b/gitrpc/internal/types/types.go index deb6c789d..0d9cff775 100644 --- a/gitrpc/internal/types/types.go +++ b/gitrpc/internal/types/types.go @@ -150,12 +150,13 @@ type Tag struct { Tagger Signature } -type CreateTagRequest struct { - Name string - TargetSha string - Message string - TaggerName string - TaggerEmail string +type CreateTagOptions struct { + // Message is the optional message the tag will be created with - if the message is empty + // the tag will be lightweight, otherwise it'll be annotated. + Message string + + // Tagger is the information used in case the tag is annotated (Message is provided). + Tagger Signature } // Signature represents the Author or Committer information. diff --git a/gitrpc/kuberesolver.go b/gitrpc/kuberesolver.go new file mode 100644 index 000000000..888a664f1 --- /dev/null +++ b/gitrpc/kuberesolver.go @@ -0,0 +1,13 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package gitrpc + +import ( + "github.com/sercand/kuberesolver/v5" +) + +func init() { + kuberesolver.RegisterInCluster() +} diff --git a/gitrpc/merge.go b/gitrpc/merge.go index 66d05b400..4a87607d9 100644 --- a/gitrpc/merge.go +++ b/gitrpc/merge.go @@ -23,13 +23,17 @@ type MergeParams struct { Title string Message string - // Committer overwrites the git committer used for committing the files (optional, default: actor) + // Committer overwrites the git committer used for committing the files + // (optional, default: actor) Committer *Identity - // CommitterDate overwrites the git committer date used for committing the files (optional, default: current time) + // CommitterDate overwrites the git committer date used for committing the files + // (optional, default: current time on server) CommitterDate *time.Time - // Author overwrites the git author used for committing the files (optional, default: committer) + // Author overwrites the git author used for committing the files + // (optional, default: committer) Author *Identity - // AuthorDate overwrites the git author date used for committing the files (optional, default: committer date) + // AuthorDate overwrites the git author date used for committing the files + // (optional, default: committer date) AuthorDate *time.Time RefType enum.RefType diff --git a/gitrpc/operations.go b/gitrpc/operations.go index 43cc22fc0..c680c7cb8 100644 --- a/gitrpc/operations.go +++ b/gitrpc/operations.go @@ -45,13 +45,17 @@ type CommitFilesParams struct { NewBranch string Actions []CommitFileAction - // Committer overwrites the git committer used for committing the files (optional, default: actor) + // Committer overwrites the git committer used for committing the files + // (optional, default: actor) Committer *Identity - // CommitterDate overwrites the git committer date used for committing the files (optional, default: current time) + // CommitterDate overwrites the git committer date used for committing the files + // (optional, default: current time on server) CommitterDate *time.Time - // Author overwrites the git author used for committing the files (optional, default: committer) + // Author overwrites the git author used for committing the files + // (optional, default: committer) Author *Identity - // AuthorDate overwrites the git author date used for committing the files (optional, default: committer date) + // AuthorDate overwrites the git author date used for committing the files + // (optional, default: committer date) AuthorDate *time.Time } diff --git a/gitrpc/proto/ref.proto b/gitrpc/proto/ref.proto index 25e5148ba..b9f1fa025 100644 --- a/gitrpc/proto/ref.proto +++ b/gitrpc/proto/ref.proto @@ -11,21 +11,22 @@ service ReferenceService { rpc DeleteBranch(DeleteBranchRequest) returns (DeleteBranchResponse); rpc ListBranches(ListBranchesRequest) returns (stream ListBranchesResponse); rpc ListCommitTags(ListCommitTagsRequest) returns (stream ListCommitTagsResponse); - rpc CreateTag(CreateTagRequest) returns (CreateTagResponse); + rpc CreateCommitTag(CreateCommitTagRequest) returns (CreateCommitTagResponse); rpc DeleteTag(DeleteTagRequest) returns (UpdateRefResponse); rpc GetRef(GetRefRequest) returns (GetRefResponse); rpc UpdateRef(UpdateRefRequest) returns (UpdateRefResponse); } -message CreateTagRequest { +message CreateCommitTagRequest { WriteRequest base = 1; - string sha = 2; - string tag_name = 3; + string tag_name = 2; + string target = 3; string message = 4; - + Identity tagger = 5; + int64 taggerDate = 6; } -message CreateTagResponse { +message CreateCommitTagResponse { CommitTag tag = 1; } diff --git a/gitrpc/repo.go b/gitrpc/repo.go index c89010a39..07285f291 100644 --- a/gitrpc/repo.go +++ b/gitrpc/repo.go @@ -34,13 +34,17 @@ type CreateRepositoryParams struct { DefaultBranch string Files []File - // Committer overwrites the git committer used for committing the files (optional, default: actor) + // Committer overwrites the git committer used for committing the files + // (optional, default: actor) Committer *Identity - // CommitterDate overwrites the git committer date used for committing the files (optional, default: current time) + // CommitterDate overwrites the git committer date used for committing the files + // (optional, default: current time on server) CommitterDate *time.Time - // Author overwrites the git author used for committing the files (optional, default: committer) + // Author overwrites the git author used for committing the files + // (optional, default: committer) Author *Identity - // AuthorDate overwrites the git author date used for committing the files (optional, default: committer date) + // AuthorDate overwrites the git author date used for committing the files + // (optional, default: committer date) AuthorDate *time.Time } diff --git a/gitrpc/rpc/http.pb.go b/gitrpc/rpc/http.pb.go index 9afe821bf..7006fe359 100644 --- a/gitrpc/rpc/http.pb.go +++ b/gitrpc/rpc/http.pb.go @@ -151,6 +151,7 @@ type ServicePackRequest struct { // Depending on the service the matching base type has to be passed // // Types that are assignable to Base: + // // *ServicePackRequest_ReadBase // *ServicePackRequest_WriteBase Base isServicePackRequest_Base `protobuf_oneof:"base"` diff --git a/gitrpc/rpc/operations.pb.go b/gitrpc/rpc/operations.pb.go index b526563df..269cec283 100644 --- a/gitrpc/rpc/operations.pb.go +++ b/gitrpc/rpc/operations.pb.go @@ -263,6 +263,7 @@ type CommitFilesAction struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Payload: + // // *CommitFilesAction_Header // *CommitFilesAction_Content Payload isCommitFilesAction_Payload `protobuf_oneof:"payload"` @@ -346,6 +347,7 @@ type CommitFilesRequest struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Payload: + // // *CommitFilesRequest_Header // *CommitFilesRequest_Action Payload isCommitFilesRequest_Payload `protobuf_oneof:"payload"` diff --git a/gitrpc/rpc/ref.pb.go b/gitrpc/rpc/ref.pb.go index 65fd62c44..431785951 100644 --- a/gitrpc/rpc/ref.pb.go +++ b/gitrpc/rpc/ref.pb.go @@ -118,19 +118,21 @@ func (ListCommitTagsRequest_SortOption) EnumDescriptor() ([]byte, []int) { return file_ref_proto_rawDescGZIP(), []int{12, 0} } -type CreateTagRequest struct { +type CreateCommitTagRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Base *WriteRequest `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` - Sha string `protobuf:"bytes,2,opt,name=sha,proto3" json:"sha,omitempty"` - TagName string `protobuf:"bytes,3,opt,name=tag_name,json=tagName,proto3" json:"tag_name,omitempty"` - Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` + Base *WriteRequest `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + TagName string `protobuf:"bytes,2,opt,name=tag_name,json=tagName,proto3" json:"tag_name,omitempty"` + Target string `protobuf:"bytes,3,opt,name=target,proto3" json:"target,omitempty"` + Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` + Tagger *Identity `protobuf:"bytes,5,opt,name=tagger,proto3" json:"tagger,omitempty"` + TaggerDate int64 `protobuf:"varint,6,opt,name=taggerDate,proto3" json:"taggerDate,omitempty"` } -func (x *CreateTagRequest) Reset() { - *x = CreateTagRequest{} +func (x *CreateCommitTagRequest) Reset() { + *x = CreateCommitTagRequest{} if protoimpl.UnsafeEnabled { mi := &file_ref_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -138,13 +140,13 @@ func (x *CreateTagRequest) Reset() { } } -func (x *CreateTagRequest) String() string { +func (x *CreateCommitTagRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateTagRequest) ProtoMessage() {} +func (*CreateCommitTagRequest) ProtoMessage() {} -func (x *CreateTagRequest) ProtoReflect() protoreflect.Message { +func (x *CreateCommitTagRequest) ProtoReflect() protoreflect.Message { mi := &file_ref_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -156,40 +158,54 @@ func (x *CreateTagRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateTagRequest.ProtoReflect.Descriptor instead. -func (*CreateTagRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateCommitTagRequest.ProtoReflect.Descriptor instead. +func (*CreateCommitTagRequest) Descriptor() ([]byte, []int) { return file_ref_proto_rawDescGZIP(), []int{0} } -func (x *CreateTagRequest) GetBase() *WriteRequest { +func (x *CreateCommitTagRequest) GetBase() *WriteRequest { if x != nil { return x.Base } return nil } -func (x *CreateTagRequest) GetSha() string { - if x != nil { - return x.Sha - } - return "" -} - -func (x *CreateTagRequest) GetTagName() string { +func (x *CreateCommitTagRequest) GetTagName() string { if x != nil { return x.TagName } return "" } -func (x *CreateTagRequest) GetMessage() string { +func (x *CreateCommitTagRequest) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *CreateCommitTagRequest) GetMessage() string { if x != nil { return x.Message } return "" } -type CreateTagResponse struct { +func (x *CreateCommitTagRequest) GetTagger() *Identity { + if x != nil { + return x.Tagger + } + return nil +} + +func (x *CreateCommitTagRequest) GetTaggerDate() int64 { + if x != nil { + return x.TaggerDate + } + return 0 +} + +type CreateCommitTagResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -197,8 +213,8 @@ type CreateTagResponse struct { Tag *CommitTag `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` } -func (x *CreateTagResponse) Reset() { - *x = CreateTagResponse{} +func (x *CreateCommitTagResponse) Reset() { + *x = CreateCommitTagResponse{} if protoimpl.UnsafeEnabled { mi := &file_ref_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -206,13 +222,13 @@ func (x *CreateTagResponse) Reset() { } } -func (x *CreateTagResponse) String() string { +func (x *CreateCommitTagResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateTagResponse) ProtoMessage() {} +func (*CreateCommitTagResponse) ProtoMessage() {} -func (x *CreateTagResponse) ProtoReflect() protoreflect.Message { +func (x *CreateCommitTagResponse) ProtoReflect() protoreflect.Message { mi := &file_ref_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -224,12 +240,12 @@ func (x *CreateTagResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateTagResponse.ProtoReflect.Descriptor instead. -func (*CreateTagResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateCommitTagResponse.ProtoReflect.Descriptor instead. +func (*CreateCommitTagResponse) Descriptor() ([]byte, []int) { return file_ref_proto_rawDescGZIP(), []int{1} } -func (x *CreateTagResponse) GetTag() *CommitTag { +func (x *CreateCommitTagResponse) GetTag() *CommitTag { if x != nil { return x.Tag } @@ -1286,184 +1302,190 @@ var File_ref_proto protoreflect.FileDescriptor var file_ref_proto_rawDesc = []byte{ 0x0a, 0x09, 0x72, 0x65, 0x66, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x72, 0x70, 0x63, - 0x1a, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x80, - 0x01, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x68, - 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x12, 0x19, 0x0a, 0x08, - 0x74, 0x61, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x74, 0x61, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x22, 0x35, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x67, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x54, 0x61, 0x67, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x54, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x54, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x04, - 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x70, 0x63, - 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, - 0x61, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x61, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x61, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x75, - 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, + 0x1a, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd3, + 0x01, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, + 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x04, 0x62, 0x61, 0x73, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x72, + 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, + 0x12, 0x19, 0x0a, 0x08, 0x74, 0x61, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x74, 0x61, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x25, 0x0a, + 0x06, 0x74, 0x61, 0x67, 0x67, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, + 0x72, 0x70, 0x63, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x74, 0x61, + 0x67, 0x67, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x61, 0x67, 0x67, 0x65, 0x72, 0x44, 0x61, + 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x61, 0x67, 0x67, 0x65, 0x72, + 0x44, 0x61, 0x74, 0x65, 0x22, 0x3b, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x20, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x52, 0x03, 0x74, 0x61, + 0x67, 0x22, 0x54, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, - 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x3b, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, - 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, - 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, - 0x72, 0x70, 0x63, 0x2e, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x06, 0x62, 0x72, 0x61, 0x6e, - 0x63, 0x68, 0x22, 0x59, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, - 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x38, 0x0a, - 0x11, 0x47, 0x65, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x23, 0x0a, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, - 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x22, 0x73, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, + 0x74, 0x61, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x74, 0x61, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x75, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x62, 0x72, 0x61, 0x6e, - 0x63, 0x68, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x22, 0x28, 0x0a, 0x14, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x68, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x22, 0xb6, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x42, - 0x72, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, - 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, - 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, - 0x62, 0x61, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x6e, - 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, - 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x12, 0x37, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x23, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x4f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x12, 0x24, 0x0a, 0x05, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, - 0x70, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, - 0x22, 0x2d, 0x0a, 0x0a, 0x53, 0x6f, 0x72, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, - 0x0a, 0x07, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, - 0x61, 0x6d, 0x65, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x65, 0x10, 0x02, 0x22, - 0x3b, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, - 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x72, - 0x61, 0x6e, 0x63, 0x68, 0x52, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x22, 0x53, 0x0a, 0x06, - 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x68, - 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x12, 0x23, 0x0a, 0x06, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x22, 0xba, 0x02, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, - 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, - 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, - 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, - 0x64, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x39, - 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x4f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x12, 0x24, 0x0a, 0x05, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, - 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, - 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, - 0x2d, 0x0a, 0x0a, 0x53, 0x6f, 0x72, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, - 0x07, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x61, - 0x6d, 0x65, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x65, 0x10, 0x02, 0x22, 0x3a, - 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x54, 0x61, 0x67, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0xd1, 0x01, 0x0a, 0x09, 0x43, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, - 0x73, 0x68, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x12, 0x21, - 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x65, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x12, 0x26, 0x0a, 0x06, 0x74, 0x61, 0x67, 0x67, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0e, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x52, 0x06, 0x74, 0x61, 0x67, 0x67, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x22, 0x79, - 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x52, 0x65, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x63, 0x68, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x3b, + 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x72, 0x61, + 0x6e, 0x63, 0x68, 0x52, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x22, 0x59, 0x0a, 0x10, 0x47, + 0x65, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, - 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x66, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x66, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x27, 0x0a, 0x08, 0x72, 0x65, 0x66, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x0c, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x66, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x07, 0x72, 0x65, 0x66, 0x54, 0x79, 0x70, 0x65, 0x22, 0x22, 0x0a, 0x0e, 0x47, 0x65, 0x74, - 0x52, 0x65, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, - 0x68, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x22, 0xb7, 0x01, - 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x25, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x11, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x66, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x66, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x08, 0x72, 0x65, 0x66, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0c, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x66, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x72, 0x65, 0x66, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, - 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x6e, 0x65, 0x77, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x6c, - 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, - 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x13, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xd3, 0x04, 0x0a, - 0x10, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, - 0x68, 0x12, 0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x72, - 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x42, 0x72, 0x61, - 0x6e, 0x63, 0x68, 0x12, 0x15, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x72, 0x61, - 0x6e, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, - 0x63, 0x68, 0x12, 0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, - 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, - 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x42, - 0x72, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x12, 0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x72, 0x61, 0x6e, - 0x63, 0x68, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x4b, - 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x73, - 0x12, 0x1a, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x09, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x67, 0x12, 0x15, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x16, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x67, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x54, 0x61, 0x67, 0x12, 0x15, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x54, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x70, - 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x52, 0x65, 0x66, 0x12, 0x12, 0x2e, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x13, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x66, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x66, 0x12, 0x15, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x70, 0x63, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x68, 0x61, 0x72, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, - 0x2f, 0x67, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x62, 0x72, 0x61, 0x6e, + 0x63, 0x68, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x38, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x42, 0x72, 0x61, + 0x6e, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x06, 0x62, + 0x72, 0x61, 0x6e, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x72, 0x70, + 0x63, 0x2e, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, + 0x22, 0x73, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x72, 0x69, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1f, + 0x0a, 0x0b, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, + 0x66, 0x6f, 0x72, 0x63, 0x65, 0x22, 0x28, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, + 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, + 0x03, 0x73, 0x68, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x22, + 0xb6, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x25, 0x0a, + 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x37, 0x0a, 0x04, 0x73, 0x6f, + 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x73, + 0x6f, 0x72, 0x74, 0x12, 0x24, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, + 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x2d, 0x0a, 0x0a, 0x53, 0x6f, 0x72, + 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x10, 0x01, 0x12, 0x08, + 0x0a, 0x04, 0x44, 0x61, 0x74, 0x65, 0x10, 0x02, 0x22, 0x3b, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, + 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x23, 0x0a, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x06, 0x62, + 0x72, 0x61, 0x6e, 0x63, 0x68, 0x22, 0x53, 0x0a, 0x06, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x68, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x73, 0x68, 0x61, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x22, 0xba, 0x02, 0x0a, 0x15, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, + 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x39, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x73, 0x6f, + 0x72, 0x74, 0x12, 0x24, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x0e, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, + 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, + 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x2d, 0x0a, 0x0a, 0x53, 0x6f, 0x72, 0x74, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x10, 0x01, 0x12, 0x08, 0x0a, + 0x04, 0x44, 0x61, 0x74, 0x65, 0x10, 0x02, 0x22, 0x3a, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x20, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x52, 0x03, + 0x74, 0x61, 0x67, 0x22, 0xd1, 0x01, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, + 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x68, 0x61, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x61, 0x6e, + 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, + 0x73, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x74, 0x61, + 0x67, 0x67, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x06, 0x74, 0x61, 0x67, 0x67, + 0x65, 0x72, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, + 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x22, 0x79, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x52, 0x65, + 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x19, + 0x0a, 0x08, 0x72, 0x65, 0x66, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x72, 0x65, 0x66, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x08, 0x72, 0x65, 0x66, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0c, 0x2e, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x65, 0x66, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x72, 0x65, 0x66, 0x54, 0x79, + 0x70, 0x65, 0x22, 0x22, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x66, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x68, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x22, 0xb7, 0x01, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x04, 0x62, + 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x70, 0x63, 0x2e, + 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, + 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x66, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x66, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, + 0x08, 0x72, 0x65, 0x66, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x0c, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x66, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x72, + 0x65, 0x66, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x65, 0x77, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0x13, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x66, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe5, 0x04, 0x0a, 0x10, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, + 0x6e, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12, 0x18, 0x2e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3a, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12, 0x15, 0x2e, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x72, 0x61, + 0x6e, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12, 0x18, 0x2e, 0x72, 0x70, + 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x45, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, + 0x12, 0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, + 0x68, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x73, 0x12, 0x1a, 0x2e, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x30, 0x01, 0x12, 0x4c, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x12, 0x1b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x61, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x67, 0x12, + 0x15, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x67, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, + 0x0a, 0x06, 0x47, 0x65, 0x74, 0x52, 0x65, 0x66, 0x12, 0x12, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x66, 0x12, 0x15, + 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x66, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, + 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x72, 0x6e, + 0x65, 0x73, 0x73, 0x2f, 0x67, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x69, 0x74, 0x72, + 0x70, 0x63, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1483,8 +1505,8 @@ var file_ref_proto_msgTypes = make([]protoimpl.MessageInfo, 19) var file_ref_proto_goTypes = []interface{}{ (ListBranchesRequest_SortOption)(0), // 0: rpc.ListBranchesRequest.SortOption (ListCommitTagsRequest_SortOption)(0), // 1: rpc.ListCommitTagsRequest.SortOption - (*CreateTagRequest)(nil), // 2: rpc.CreateTagRequest - (*CreateTagResponse)(nil), // 3: rpc.CreateTagResponse + (*CreateCommitTagRequest)(nil), // 2: rpc.CreateCommitTagRequest + (*CreateCommitTagResponse)(nil), // 3: rpc.CreateCommitTagResponse (*DeleteTagRequest)(nil), // 4: rpc.DeleteTagRequest (*CreateBranchRequest)(nil), // 5: rpc.CreateBranchRequest (*CreateBranchResponse)(nil), // 6: rpc.CreateBranchResponse @@ -1503,59 +1525,61 @@ var file_ref_proto_goTypes = []interface{}{ (*UpdateRefRequest)(nil), // 19: rpc.UpdateRefRequest (*UpdateRefResponse)(nil), // 20: rpc.UpdateRefResponse (*WriteRequest)(nil), // 21: rpc.WriteRequest - (*ReadRequest)(nil), // 22: rpc.ReadRequest - (SortOrder)(0), // 23: rpc.SortOrder - (*Commit)(nil), // 24: rpc.Commit - (*Signature)(nil), // 25: rpc.Signature - (RefType)(0), // 26: rpc.RefType + (*Identity)(nil), // 22: rpc.Identity + (*ReadRequest)(nil), // 23: rpc.ReadRequest + (SortOrder)(0), // 24: rpc.SortOrder + (*Commit)(nil), // 25: rpc.Commit + (*Signature)(nil), // 26: rpc.Signature + (RefType)(0), // 27: rpc.RefType } var file_ref_proto_depIdxs = []int32{ - 21, // 0: rpc.CreateTagRequest.base:type_name -> rpc.WriteRequest - 16, // 1: rpc.CreateTagResponse.tag:type_name -> rpc.CommitTag - 21, // 2: rpc.DeleteTagRequest.base:type_name -> rpc.WriteRequest - 21, // 3: rpc.CreateBranchRequest.base:type_name -> rpc.WriteRequest - 13, // 4: rpc.CreateBranchResponse.branch:type_name -> rpc.Branch - 22, // 5: rpc.GetBranchRequest.base:type_name -> rpc.ReadRequest - 13, // 6: rpc.GetBranchResponse.branch:type_name -> rpc.Branch - 21, // 7: rpc.DeleteBranchRequest.base:type_name -> rpc.WriteRequest - 22, // 8: rpc.ListBranchesRequest.base:type_name -> rpc.ReadRequest - 0, // 9: rpc.ListBranchesRequest.sort:type_name -> rpc.ListBranchesRequest.SortOption - 23, // 10: rpc.ListBranchesRequest.order:type_name -> rpc.SortOrder - 13, // 11: rpc.ListBranchesResponse.branch:type_name -> rpc.Branch - 24, // 12: rpc.Branch.commit:type_name -> rpc.Commit - 22, // 13: rpc.ListCommitTagsRequest.base:type_name -> rpc.ReadRequest - 1, // 14: rpc.ListCommitTagsRequest.sort:type_name -> rpc.ListCommitTagsRequest.SortOption - 23, // 15: rpc.ListCommitTagsRequest.order:type_name -> rpc.SortOrder - 16, // 16: rpc.ListCommitTagsResponse.tag:type_name -> rpc.CommitTag - 25, // 17: rpc.CommitTag.tagger:type_name -> rpc.Signature - 24, // 18: rpc.CommitTag.commit:type_name -> rpc.Commit - 22, // 19: rpc.GetRefRequest.base:type_name -> rpc.ReadRequest - 26, // 20: rpc.GetRefRequest.ref_type:type_name -> rpc.RefType - 21, // 21: rpc.UpdateRefRequest.base:type_name -> rpc.WriteRequest - 26, // 22: rpc.UpdateRefRequest.ref_type:type_name -> rpc.RefType - 5, // 23: rpc.ReferenceService.CreateBranch:input_type -> rpc.CreateBranchRequest - 7, // 24: rpc.ReferenceService.GetBranch:input_type -> rpc.GetBranchRequest - 9, // 25: rpc.ReferenceService.DeleteBranch:input_type -> rpc.DeleteBranchRequest - 11, // 26: rpc.ReferenceService.ListBranches:input_type -> rpc.ListBranchesRequest - 14, // 27: rpc.ReferenceService.ListCommitTags:input_type -> rpc.ListCommitTagsRequest - 2, // 28: rpc.ReferenceService.CreateTag:input_type -> rpc.CreateTagRequest - 4, // 29: rpc.ReferenceService.DeleteTag:input_type -> rpc.DeleteTagRequest - 17, // 30: rpc.ReferenceService.GetRef:input_type -> rpc.GetRefRequest - 19, // 31: rpc.ReferenceService.UpdateRef:input_type -> rpc.UpdateRefRequest - 6, // 32: rpc.ReferenceService.CreateBranch:output_type -> rpc.CreateBranchResponse - 8, // 33: rpc.ReferenceService.GetBranch:output_type -> rpc.GetBranchResponse - 10, // 34: rpc.ReferenceService.DeleteBranch:output_type -> rpc.DeleteBranchResponse - 12, // 35: rpc.ReferenceService.ListBranches:output_type -> rpc.ListBranchesResponse - 15, // 36: rpc.ReferenceService.ListCommitTags:output_type -> rpc.ListCommitTagsResponse - 3, // 37: rpc.ReferenceService.CreateTag:output_type -> rpc.CreateTagResponse - 20, // 38: rpc.ReferenceService.DeleteTag:output_type -> rpc.UpdateRefResponse - 18, // 39: rpc.ReferenceService.GetRef:output_type -> rpc.GetRefResponse - 20, // 40: rpc.ReferenceService.UpdateRef:output_type -> rpc.UpdateRefResponse - 32, // [32:41] is the sub-list for method output_type - 23, // [23:32] is the sub-list for method input_type - 23, // [23:23] is the sub-list for extension type_name - 23, // [23:23] is the sub-list for extension extendee - 0, // [0:23] is the sub-list for field type_name + 21, // 0: rpc.CreateCommitTagRequest.base:type_name -> rpc.WriteRequest + 22, // 1: rpc.CreateCommitTagRequest.tagger:type_name -> rpc.Identity + 16, // 2: rpc.CreateCommitTagResponse.tag:type_name -> rpc.CommitTag + 21, // 3: rpc.DeleteTagRequest.base:type_name -> rpc.WriteRequest + 21, // 4: rpc.CreateBranchRequest.base:type_name -> rpc.WriteRequest + 13, // 5: rpc.CreateBranchResponse.branch:type_name -> rpc.Branch + 23, // 6: rpc.GetBranchRequest.base:type_name -> rpc.ReadRequest + 13, // 7: rpc.GetBranchResponse.branch:type_name -> rpc.Branch + 21, // 8: rpc.DeleteBranchRequest.base:type_name -> rpc.WriteRequest + 23, // 9: rpc.ListBranchesRequest.base:type_name -> rpc.ReadRequest + 0, // 10: rpc.ListBranchesRequest.sort:type_name -> rpc.ListBranchesRequest.SortOption + 24, // 11: rpc.ListBranchesRequest.order:type_name -> rpc.SortOrder + 13, // 12: rpc.ListBranchesResponse.branch:type_name -> rpc.Branch + 25, // 13: rpc.Branch.commit:type_name -> rpc.Commit + 23, // 14: rpc.ListCommitTagsRequest.base:type_name -> rpc.ReadRequest + 1, // 15: rpc.ListCommitTagsRequest.sort:type_name -> rpc.ListCommitTagsRequest.SortOption + 24, // 16: rpc.ListCommitTagsRequest.order:type_name -> rpc.SortOrder + 16, // 17: rpc.ListCommitTagsResponse.tag:type_name -> rpc.CommitTag + 26, // 18: rpc.CommitTag.tagger:type_name -> rpc.Signature + 25, // 19: rpc.CommitTag.commit:type_name -> rpc.Commit + 23, // 20: rpc.GetRefRequest.base:type_name -> rpc.ReadRequest + 27, // 21: rpc.GetRefRequest.ref_type:type_name -> rpc.RefType + 21, // 22: rpc.UpdateRefRequest.base:type_name -> rpc.WriteRequest + 27, // 23: rpc.UpdateRefRequest.ref_type:type_name -> rpc.RefType + 5, // 24: rpc.ReferenceService.CreateBranch:input_type -> rpc.CreateBranchRequest + 7, // 25: rpc.ReferenceService.GetBranch:input_type -> rpc.GetBranchRequest + 9, // 26: rpc.ReferenceService.DeleteBranch:input_type -> rpc.DeleteBranchRequest + 11, // 27: rpc.ReferenceService.ListBranches:input_type -> rpc.ListBranchesRequest + 14, // 28: rpc.ReferenceService.ListCommitTags:input_type -> rpc.ListCommitTagsRequest + 2, // 29: rpc.ReferenceService.CreateCommitTag:input_type -> rpc.CreateCommitTagRequest + 4, // 30: rpc.ReferenceService.DeleteTag:input_type -> rpc.DeleteTagRequest + 17, // 31: rpc.ReferenceService.GetRef:input_type -> rpc.GetRefRequest + 19, // 32: rpc.ReferenceService.UpdateRef:input_type -> rpc.UpdateRefRequest + 6, // 33: rpc.ReferenceService.CreateBranch:output_type -> rpc.CreateBranchResponse + 8, // 34: rpc.ReferenceService.GetBranch:output_type -> rpc.GetBranchResponse + 10, // 35: rpc.ReferenceService.DeleteBranch:output_type -> rpc.DeleteBranchResponse + 12, // 36: rpc.ReferenceService.ListBranches:output_type -> rpc.ListBranchesResponse + 15, // 37: rpc.ReferenceService.ListCommitTags:output_type -> rpc.ListCommitTagsResponse + 3, // 38: rpc.ReferenceService.CreateCommitTag:output_type -> rpc.CreateCommitTagResponse + 20, // 39: rpc.ReferenceService.DeleteTag:output_type -> rpc.UpdateRefResponse + 18, // 40: rpc.ReferenceService.GetRef:output_type -> rpc.GetRefResponse + 20, // 41: rpc.ReferenceService.UpdateRef:output_type -> rpc.UpdateRefResponse + 33, // [33:42] is the sub-list for method output_type + 24, // [24:33] is the sub-list for method input_type + 24, // [24:24] is the sub-list for extension type_name + 24, // [24:24] is the sub-list for extension extendee + 0, // [0:24] is the sub-list for field type_name } func init() { file_ref_proto_init() } @@ -1566,7 +1590,7 @@ func file_ref_proto_init() { file_shared_proto_init() if !protoimpl.UnsafeEnabled { file_ref_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateTagRequest); i { + switch v := v.(*CreateCommitTagRequest); i { case 0: return &v.state case 1: @@ -1578,7 +1602,7 @@ func file_ref_proto_init() { } } file_ref_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateTagResponse); i { + switch v := v.(*CreateCommitTagResponse); i { case 0: return &v.state case 1: diff --git a/gitrpc/rpc/ref_grpc.pb.go b/gitrpc/rpc/ref_grpc.pb.go index 1056d1a5f..ea826ae40 100644 --- a/gitrpc/rpc/ref_grpc.pb.go +++ b/gitrpc/rpc/ref_grpc.pb.go @@ -27,7 +27,7 @@ type ReferenceServiceClient interface { DeleteBranch(ctx context.Context, in *DeleteBranchRequest, opts ...grpc.CallOption) (*DeleteBranchResponse, error) ListBranches(ctx context.Context, in *ListBranchesRequest, opts ...grpc.CallOption) (ReferenceService_ListBranchesClient, error) ListCommitTags(ctx context.Context, in *ListCommitTagsRequest, opts ...grpc.CallOption) (ReferenceService_ListCommitTagsClient, error) - CreateTag(ctx context.Context, in *CreateTagRequest, opts ...grpc.CallOption) (*CreateTagResponse, error) + CreateCommitTag(ctx context.Context, in *CreateCommitTagRequest, opts ...grpc.CallOption) (*CreateCommitTagResponse, error) DeleteTag(ctx context.Context, in *DeleteTagRequest, opts ...grpc.CallOption) (*UpdateRefResponse, error) GetRef(ctx context.Context, in *GetRefRequest, opts ...grpc.CallOption) (*GetRefResponse, error) UpdateRef(ctx context.Context, in *UpdateRefRequest, opts ...grpc.CallOption) (*UpdateRefResponse, error) @@ -132,9 +132,9 @@ func (x *referenceServiceListCommitTagsClient) Recv() (*ListCommitTagsResponse, return m, nil } -func (c *referenceServiceClient) CreateTag(ctx context.Context, in *CreateTagRequest, opts ...grpc.CallOption) (*CreateTagResponse, error) { - out := new(CreateTagResponse) - err := c.cc.Invoke(ctx, "/rpc.ReferenceService/CreateTag", in, out, opts...) +func (c *referenceServiceClient) CreateCommitTag(ctx context.Context, in *CreateCommitTagRequest, opts ...grpc.CallOption) (*CreateCommitTagResponse, error) { + out := new(CreateCommitTagResponse) + err := c.cc.Invoke(ctx, "/rpc.ReferenceService/CreateCommitTag", in, out, opts...) if err != nil { return nil, err } @@ -177,7 +177,7 @@ type ReferenceServiceServer interface { DeleteBranch(context.Context, *DeleteBranchRequest) (*DeleteBranchResponse, error) ListBranches(*ListBranchesRequest, ReferenceService_ListBranchesServer) error ListCommitTags(*ListCommitTagsRequest, ReferenceService_ListCommitTagsServer) error - CreateTag(context.Context, *CreateTagRequest) (*CreateTagResponse, error) + CreateCommitTag(context.Context, *CreateCommitTagRequest) (*CreateCommitTagResponse, error) DeleteTag(context.Context, *DeleteTagRequest) (*UpdateRefResponse, error) GetRef(context.Context, *GetRefRequest) (*GetRefResponse, error) UpdateRef(context.Context, *UpdateRefRequest) (*UpdateRefResponse, error) @@ -203,8 +203,8 @@ func (UnimplementedReferenceServiceServer) ListBranches(*ListBranchesRequest, Re func (UnimplementedReferenceServiceServer) ListCommitTags(*ListCommitTagsRequest, ReferenceService_ListCommitTagsServer) error { return status.Errorf(codes.Unimplemented, "method ListCommitTags not implemented") } -func (UnimplementedReferenceServiceServer) CreateTag(context.Context, *CreateTagRequest) (*CreateTagResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateTag not implemented") +func (UnimplementedReferenceServiceServer) CreateCommitTag(context.Context, *CreateCommitTagRequest) (*CreateCommitTagResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateCommitTag not implemented") } func (UnimplementedReferenceServiceServer) DeleteTag(context.Context, *DeleteTagRequest) (*UpdateRefResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method DeleteTag not implemented") @@ -324,20 +324,20 @@ func (x *referenceServiceListCommitTagsServer) Send(m *ListCommitTagsResponse) e return x.ServerStream.SendMsg(m) } -func _ReferenceService_CreateTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateTagRequest) +func _ReferenceService_CreateCommitTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateCommitTagRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(ReferenceServiceServer).CreateTag(ctx, in) + return srv.(ReferenceServiceServer).CreateCommitTag(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/rpc.ReferenceService/CreateTag", + FullMethod: "/rpc.ReferenceService/CreateCommitTag", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ReferenceServiceServer).CreateTag(ctx, req.(*CreateTagRequest)) + return srv.(ReferenceServiceServer).CreateCommitTag(ctx, req.(*CreateCommitTagRequest)) } return interceptor(ctx, in, info, handler) } @@ -416,8 +416,8 @@ var ReferenceService_ServiceDesc = grpc.ServiceDesc{ Handler: _ReferenceService_DeleteBranch_Handler, }, { - MethodName: "CreateTag", - Handler: _ReferenceService_CreateTag_Handler, + MethodName: "CreateCommitTag", + Handler: _ReferenceService_CreateCommitTag_Handler, }, { MethodName: "DeleteTag", diff --git a/gitrpc/rpc/repo.pb.go b/gitrpc/rpc/repo.pb.go index 22a1c8932..5c9f42d3e 100644 --- a/gitrpc/rpc/repo.pb.go +++ b/gitrpc/rpc/repo.pb.go @@ -216,6 +216,7 @@ type CreateRepositoryRequest struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Data: + // // *CreateRepositoryRequest_Header // *CreateRepositoryRequest_File Data isCreateRepositoryRequest_Data `protobuf_oneof:"data"` @@ -1162,6 +1163,7 @@ type GetBlobResponse struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Data: + // // *GetBlobResponse_Header // *GetBlobResponse_Content Data isGetBlobResponse_Data `protobuf_oneof:"data"` diff --git a/gitrpc/rpc/shared.pb.go b/gitrpc/rpc/shared.pb.go index 084d3dfb6..65817ec5b 100644 --- a/gitrpc/rpc/shared.pb.go +++ b/gitrpc/rpc/shared.pb.go @@ -298,6 +298,7 @@ type FileUpload struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Data: + // // *FileUpload_Header // *FileUpload_Chunk Data isFileUpload_Data `protobuf_oneof:"data"` diff --git a/gitrpc/server/config.go b/gitrpc/server/config.go index 364d1100b..46ecedf6a 100644 --- a/gitrpc/server/config.go +++ b/gitrpc/server/config.go @@ -6,6 +6,7 @@ package server import ( "errors" + "time" ) // Config represents the configuration for the gitrpc server. @@ -22,6 +23,8 @@ type Config struct { HTTP struct { Bind string `envconfig:"GITRPC_SERVER_HTTP_BIND" default:":4001"` } + MaxConnAge time.Duration `envconfig:"GITRPC_SERVER_MAX_CONN_AGE" default:"630720000s"` + MaxConnAgeGrace time.Duration `envconfig:"GITRPC_SERVER_MAX_CONN_AGE_GRACE" default:"630720000s"` } func (c *Config) Validate() error { @@ -37,6 +40,12 @@ func (c *Config) Validate() error { if c.GitHookPath == "" { return errors.New("config.GitHookPath is required") } + if c.MaxConnAge == 0 { + return errors.New("config.MaxConnAge is required") + } + if c.MaxConnAgeGrace == 0 { + return errors.New("config.MaxConnAgeGrace is required") + } return nil } diff --git a/gitrpc/server/server.go b/gitrpc/server/server.go index 60318ea53..b24378c57 100644 --- a/gitrpc/server/server.go +++ b/gitrpc/server/server.go @@ -19,6 +19,7 @@ import ( grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" "google.golang.org/grpc" + "google.golang.org/grpc/keepalive" ) const ( @@ -58,6 +59,10 @@ func NewServer(config Config, adapter service.GitAdapter) (*GRPCServer, error) { logIntc.StreamInterceptor(), errIntc.StreamInterceptor(), )), + grpc.KeepaliveParams(keepalive.ServerParameters{ + MaxConnectionAge: config.MaxConnAge, + MaxConnectionAgeGrace: config.MaxConnAgeGrace, + }), ) store := storage.NewLocalStore() // create a temp dir for deleted repositories diff --git a/gitrpc/tag.go b/gitrpc/tag.go index 738419f4b..b971d6e64 100644 --- a/gitrpc/tag.go +++ b/gitrpc/tag.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "time" "github.com/harness/gitness/gitrpc/rpc" @@ -47,14 +48,26 @@ type CommitTag struct { Commit *Commit } -type CreateTagParams struct { +type CreateCommitTagParams struct { WriteParams - Name string - SHA string + Name string + + // Target is the commit (or points to the commit) the new tag will be pointing to. + Target string + + // Message is the optional message the tag will be created with - if the message is empty + // the tag will be lightweight, otherwise it'll be annotated Message string + + // Tagger overwrites the git author used in case the tag is annotated + // (optional, default: actor) + Tagger *Identity + // TaggerDate overwrites the git author date used in case the tag is annotated + // (optional, default: current time on server) + TaggerDate *time.Time } -func (p *CreateTagParams) Validate() error { +func (p *CreateCommitTagParams) Validate() error { if p == nil { return ErrNoParamsProvided } @@ -62,16 +75,14 @@ func (p *CreateTagParams) Validate() error { if p.Name == "" { return errors.New("tag name cannot be empty") } - if p.SHA == "" { + if p.Target == "" { return errors.New("target cannot be empty") } - if p.Message == "" { - return errors.New("message cannot be empty") - } + return nil } -type CreateTagOutput struct { +type CreateCommitTagOutput struct { CommitTag } @@ -139,18 +150,20 @@ func (c *Client) ListCommitTags(ctx context.Context, params *ListCommitTagsParam return output, nil } -func (c *Client) CreateTag(ctx context.Context, params *CreateTagParams) (*CreateTagOutput, error) { +func (c *Client) CreateCommitTag(ctx context.Context, params *CreateCommitTagParams) (*CreateCommitTagOutput, error) { err := params.Validate() if err != nil { return nil, err } - resp, err := c.refService.CreateTag(ctx, &rpc.CreateTagRequest{ - Base: mapToRPCWriteRequest(params.WriteParams), - Sha: params.SHA, - TagName: params.Name, - Message: params.Message, + resp, err := c.refService.CreateCommitTag(ctx, &rpc.CreateCommitTagRequest{ + Base: mapToRPCWriteRequest(params.WriteParams), + Target: params.Target, + TagName: params.Name, + Message: params.Message, + Tagger: mapToRPCIdentityOptional(params.Tagger), + TaggerDate: mapToRPCTimeOptional(params.TaggerDate), }) if err != nil { @@ -163,7 +176,7 @@ func (c *Client) CreateTag(ctx context.Context, params *CreateTagParams) (*Creat return nil, fmt.Errorf("failed to map rpc tag: %w", err) } - return &CreateTagOutput{ + return &CreateCommitTagOutput{ CommitTag: *commitTag, }, nil } diff --git a/go.mod b/go.mod index 191b37f29..32ed581a0 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/robfig/cron/v3 v3.0.0 github.com/rs/xid v1.4.0 github.com/rs/zerolog v1.29.0 + github.com/sercand/kuberesolver/v5 v5.1.0 github.com/stretchr/testify v1.8.1 github.com/swaggest/openapi-go v0.2.23 github.com/swaggest/swgui v1.4.2 @@ -44,23 +45,30 @@ require ( golang.org/x/crypto v0.1.0 golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a golang.org/x/sync v0.1.0 - golang.org/x/term v0.2.0 - golang.org/x/text v0.4.0 - google.golang.org/grpc v1.50.1 - google.golang.org/protobuf v1.28.1 + golang.org/x/term v0.6.0 + golang.org/x/text v0.8.0 + google.golang.org/grpc v1.55.0 + google.golang.org/protobuf v1.30.0 gopkg.in/alecthomas/kingpin.v2 v2.2.6 ) require ( - cloud.google.com/go v0.105.0 // indirect - cloud.google.com/go/compute v1.12.1 // indirect - cloud.google.com/go/compute/metadata v0.2.1 // indirect + cloud.google.com/go v0.110.0 // indirect + cloud.google.com/go/compute v1.18.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect - go.opencensus.io v0.23.0 // indirect - golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect - google.golang.org/api v0.102.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/prometheus/client_golang v1.15.1 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + go.opencensus.io v0.24.0 // indirect + golang.org/x/oauth2 v0.6.0 // indirect + google.golang.org/api v0.110.0 // indirect google.golang.org/appengine v1.6.7 // indirect ) @@ -77,8 +85,8 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect - github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/djherbis/buffer v1.2.0 // indirect @@ -91,7 +99,7 @@ require ( github.com/go-git/go-git/v5 v5.4.3-0.20210630082519-b4368b2a2ca4 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.1 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c // indirect github.com/google/subcommands v1.0.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect @@ -115,11 +123,11 @@ require ( github.com/xanzy/ssh-agent v0.3.1 // indirect github.com/yuin/goldmark v1.4.13 // indirect go.uber.org/atomic v1.10.0 // indirect - golang.org/x/mod v0.6.0 // indirect - golang.org/x/net v0.2.0 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.8.0 // indirect golang.org/x/sys v0.6.0 // indirect - golang.org/x/tools v0.2.0 // indirect - google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect + golang.org/x/tools v0.6.0 // indirect + google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index a85ef5e3c..2700ba03a 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,16 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= -cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= -cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/iam v0.12.0 h1:DRtTY29b75ciH6Ov1PHb4/iat2CLCvrOm40Q0a6DFpE= +cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= cloud.google.com/go/profiler v0.3.1 h1:b5got9Be9Ia0HVvyt7PavWxXEht15B9lWnigdvHtxOc= cloud.google.com/go/profiler v0.3.1/go.mod h1:GsG14VnmcMFQ9b+kq71wh3EKMZr3WRMgLzNiFRpW7tE= +cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI= code.gitea.io/gitea v1.17.2 h1:NRcVr07jF+za4d0NZZlJXeCuQK5FfHMtjPDjq4u3UiY= code.gitea.io/gitea v1.17.2/go.mod h1:sovminOoSsc8IC2T29rX9+MmaboHTu8QDEvJjaSqIXg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -20,7 +23,6 @@ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugX github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 h1:cSHEbLj0GZeHM1mWG84qEnGFojNEQ83W7cwaPRjcwXU= github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= @@ -37,13 +39,12 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4= -github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -57,6 +58,7 @@ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bool64/dev v0.1.41/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU= @@ -66,22 +68,16 @@ github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= @@ -124,9 +120,6 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -134,7 +127,8 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw= @@ -212,6 +206,7 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -221,8 +216,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= @@ -236,14 +232,11 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20220509035851-59ca7ad80af3 h1:vFrXU7L2gqtlP/ZGijSpaDIc16ZQrZI4FAuYtpQTyQc= -github.com/google/pprof v0.0.0-20220509035851-59ca7ad80af3/go.mod h1:Pt31oes+eGImORns3McJn8zHefuQl2rG8l6xQjGYB4U= github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c h1:lvddKcYTQ545ADhBujtIJmqQrZBDsGo7XIMbAQe/sNY= github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -251,10 +244,11 @@ github.com/google/subcommands v1.0.1 h1:/eqq+otEXm5vhfBrbREPCSVQbvofip6kIz+mX5TU github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= -github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -269,7 +263,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaW github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/guregu/null v4.0.0+incompatible h1:4zw0ckM7ECd6FNNddc3Fu4aty9nTlpkkzH7dPn4/4Gw= github.com/guregu/null v4.0.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= github.com/harness/go-rbac v0.0.0-20230409233212-ca97fe90aac8 h1:sQzaA/ithB9mCXTC5VeC4XTWmQ531Tefbgxr1X4y7WU= @@ -397,7 +390,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -450,6 +443,8 @@ github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -527,23 +522,30 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= @@ -558,6 +560,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sercand/kuberesolver/v5 v5.1.0 h1:YLqreB1vUFbZHSidcqI5ChMp+RIRmop0brQOeUFWiRw= +github.com/sercand/kuberesolver/v5 v5.1.0/go.mod h1:Fs1KbKhVRnB2aDWN12NjKCB+RgYMWZJ294T3BtmVCpQ= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -576,7 +580,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -636,9 +639,8 @@ go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mI go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -690,8 +692,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -711,7 +713,6 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -723,13 +724,12 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211105192438-b53810dc28af/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -789,22 +789,22 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -830,17 +830,18 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/api v0.102.0 h1:JxJl2qQ85fRMPNvlZY/enexbxpCjLwGhZUtgfGeQ51I= -google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.110.0 h1:l+rh0KYUooe9JGbGVx71tbFo4SMbMTXK3I3ia2QSEeU= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -852,12 +853,9 @@ google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e h1:S9GbmC1iCgvbLyAokVCwiO6tVIrU9Y7c5oMx1V/ki/Y= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -870,14 +868,9 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= -google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -889,10 +882,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -916,7 +908,6 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/api/auth/auth.go b/internal/api/auth/auth.go index d19e8dce1..499164c04 100644 --- a/internal/api/auth/auth.go +++ b/internal/api/auth/auth.go @@ -25,13 +25,12 @@ var ( ErrParentResourceTypeUnknown = errors.New("Unknown parent resource type") ) -/* - * Check checks if a resource specific permission is granted for the current auth session in the scope. - * Returns nil if the permission is granted, otherwise returns an error. - * NotAuthenticated, NotAuthorized, or any unerlaying error. - */ +// Check checks if a resource specific permission is granted for the current auth session in the scope. +// Returns nil if the permission is granted, otherwise returns an error. +// NotAuthenticated, NotAuthorized, or any underlying error. func Check(ctx context.Context, authorizer authz.Authorizer, session *auth.Session, - scope *types.Scope, resource *types.Resource, permission enum.Permission) error { + scope *types.Scope, resource *types.Resource, permission enum.Permission, +) error { if session == nil { return ErrNotAuthenticated } @@ -53,12 +52,10 @@ func Check(ctx context.Context, authorizer authz.Authorizer, session *auth.Sessi return nil } -/* - * CheckChild checks if a resource specific permission is granted for the current auth session - * in the scope of a parent. - * Returns nil if the permission is granted, otherwise returns an error. - * NotAuthenticated, NotAuthorized, or any unerlaying error. - */ +// CheckChild checks if a resource specific permission is granted for the current auth session +// in the scope of a parent. +// Returns nil if the permission is granted, otherwise returns an error. +// NotAuthenticated, NotAuthorized, or any underlying error. func CheckChild(ctx context.Context, authorizer authz.Authorizer, session *auth.Session, spaceStore store.SpaceStore, repoStore store.RepoStore, parentType enum.ParentResourceType, parentID int64, resourceType enum.ResourceType, resourceName string, permission enum.Permission) error { diff --git a/internal/api/auth/repo.go b/internal/api/auth/repo.go index ddf0a8b8d..67b8c454b 100644 --- a/internal/api/auth/repo.go +++ b/internal/api/auth/repo.go @@ -16,14 +16,13 @@ import ( "github.com/pkg/errors" ) -/* - * CheckRepo checks if a repo specific permission is granted for the current auth session - * in the scope of its parent. - * Returns nil if the permission is granted, otherwise returns an error. - * NotAuthenticated, NotAuthorized, or any unerlaying error. - */ +// CheckRepo checks if a repo specific permission is granted for the current auth session +// in the scope of its parent. +// Returns nil if the permission is granted, otherwise returns an error. +// NotAuthenticated, NotAuthorized, or any underlying error. func CheckRepo(ctx context.Context, authorizer authz.Authorizer, session *auth.Session, - repo *types.Repository, permission enum.Permission, orPublic bool) error { + repo *types.Repository, permission enum.Permission, orPublic bool, +) error { if orPublic && repo.IsPublic { return nil } diff --git a/internal/api/auth/service.go b/internal/api/auth/service.go index 03673bcc3..acc21fc24 100644 --- a/internal/api/auth/service.go +++ b/internal/api/auth/service.go @@ -13,14 +13,13 @@ import ( "github.com/harness/gitness/types/enum" ) -/* - * CheckService checks if a service specific permission is granted for the current auth session. - * Returns nil if the permission is granted, otherwise returns an error. - * NotAuthenticated, NotAuthorized, or any unerlaying error. - */ +// CheckService checks if a service specific permission is granted for the current auth session. +// Returns nil if the permission is granted, otherwise returns an error. +// NotAuthenticated, NotAuthorized, or any underlying error. func CheckService(ctx context.Context, authorizer authz.Authorizer, session *auth.Session, - svc *types.Service, permission enum.Permission) error { - // a service exists outside of any scope + svc *types.Service, permission enum.Permission, +) error { + // a service exists outside any scope scope := &types.Scope{} resource := &types.Resource{ Type: enum.ResourceTypeService, diff --git a/internal/api/auth/serviceAccount.go b/internal/api/auth/service_account.go similarity index 66% rename from internal/api/auth/serviceAccount.go rename to internal/api/auth/service_account.go index 7a9c65a3b..332e79bc2 100644 --- a/internal/api/auth/serviceAccount.go +++ b/internal/api/auth/service_account.go @@ -13,15 +13,15 @@ import ( "github.com/harness/gitness/types/enum" ) -/* - * CheckServiceAccount checks if a service account specific permission is granted for the current auth session - * in the scope of the parent. - * Returns nil if the permission is granted, otherwise returns an error. - * NotAuthenticated, NotAuthorized, or any unerlaying error. - */ +// CheckServiceAccount checks if a service account specific permission is granted for the current auth session +// in the scope of the parent. +// Returns nil if the permission is granted, otherwise returns an error. +// NotAuthenticated, NotAuthorized, or any underlying error. func CheckServiceAccount(ctx context.Context, authorizer authz.Authorizer, session *auth.Session, spaceStore store.SpaceStore, repoStore store.RepoStore, parentType enum.ParentResourceType, parentID int64, - saUID string, permission enum.Permission) error { - return CheckChild(ctx, authorizer, session, spaceStore, repoStore, parentType, parentID, + saUID string, permission enum.Permission, +) error { + return CheckChild(ctx, authorizer, session, + spaceStore, repoStore, parentType, parentID, enum.ResourceTypeServiceAccount, saUID, permission) } diff --git a/internal/api/auth/space.go b/internal/api/auth/space.go index 78e46bded..68c28ebe3 100644 --- a/internal/api/auth/space.go +++ b/internal/api/auth/space.go @@ -16,14 +16,13 @@ import ( "github.com/pkg/errors" ) -/* - * CheckSpace checks if a space specific permission is granted for the current auth session - * in the scope of its parent. - * Returns nil if the permission is granted, otherwise returns an error. - * NotAuthenticated, NotAuthorized, or any unerlaying error. - */ +// CheckSpace checks if a space specific permission is granted for the current auth session +// in the scope of its parent. +// Returns nil if the permission is granted, otherwise returns an error. +// NotAuthenticated, NotAuthorized, or any underlying error. func CheckSpace(ctx context.Context, authorizer authz.Authorizer, session *auth.Session, - space *types.Space, permission enum.Permission, orPublic bool) error { + space *types.Space, permission enum.Permission, orPublic bool, +) error { if orPublic && space.IsPublic { return nil } diff --git a/internal/api/auth/user.go b/internal/api/auth/user.go index c0f766d51..d795eb5e3 100644 --- a/internal/api/auth/user.go +++ b/internal/api/auth/user.go @@ -13,14 +13,13 @@ import ( "github.com/harness/gitness/types/enum" ) -/* - * CheckUser checks if a user specific permission is granted for the current auth session. - * Returns nil if the permission is granted, otherwise returns an error. - * NotAuthenticated, NotAuthorized, or any unerlaying error. - */ +// CheckUser checks if a user specific permission is granted for the current auth session. +// Returns nil if the permission is granted, otherwise returns an error. +// NotAuthenticated, NotAuthorized, or any underlying error. func CheckUser(ctx context.Context, authorizer authz.Authorizer, session *auth.Session, - user *types.User, permission enum.Permission) error { - // a user exists outside of any scope + user *types.User, permission enum.Permission, +) error { + // a user exists outside any scope scope := &types.Scope{} resource := &types.Resource{ Type: enum.ResourceTypeUser, diff --git a/internal/api/controller/check/controller.go b/internal/api/controller/check/controller.go index 3c866f170..3c3055156 100644 --- a/internal/api/controller/check/controller.go +++ b/internal/api/controller/check/controller.go @@ -23,8 +23,8 @@ import ( type Controller struct { db *sqlx.DB authorizer authz.Authorizer - checkStore store.CheckStore repoStore store.RepoStore + checkStore store.CheckStore gitRPCClient gitrpc.Interface } @@ -32,12 +32,14 @@ func NewController( db *sqlx.DB, authorizer authz.Authorizer, repoStore store.RepoStore, + checkStore store.CheckStore, gitRPCClient gitrpc.Interface, ) *Controller { return &Controller{ db: db, authorizer: authorizer, repoStore: repoStore, + checkStore: checkStore, gitRPCClient: gitRPCClient, } } diff --git a/internal/api/controller/check/wire.go b/internal/api/controller/check/wire.go index dacda81a2..6a15eef0c 100644 --- a/internal/api/controller/check/wire.go +++ b/internal/api/controller/check/wire.go @@ -18,11 +18,18 @@ var WireSet = wire.NewSet( ProvideController, ) -func ProvideController(db *sqlx.DB, authorizer authz.Authorizer, +func ProvideController( + db *sqlx.DB, + authorizer authz.Authorizer, repoStore store.RepoStore, + checkStore store.CheckStore, rpcClient gitrpc.Interface, ) *Controller { - return NewController(db, authorizer, + return NewController( + db, + authorizer, repoStore, - rpcClient) + checkStore, + rpcClient, + ) } diff --git a/internal/api/controller/pullreq/merge.go b/internal/api/controller/pullreq/merge.go index 219e599b8..269309757 100644 --- a/internal/api/controller/pullreq/merge.go +++ b/internal/api/controller/pullreq/merge.go @@ -83,12 +83,6 @@ func (c *Controller) Merge( return types.MergeResponse{}, usererror.BadRequest("Pull request must be open") } - if pr.UnresolvedCount > 0 { - return types.MergeResponse{}, usererror.BadRequest( - "Pull requests with unresolved comments can't be merged. Resolve all the comments first.", - ) - } - if pr.IsDraft { return types.MergeResponse{}, usererror.BadRequest( "Draft pull requests can't be merged. Clear the draft flag first.", diff --git a/internal/api/controller/pullreq/pr_diff.go b/internal/api/controller/pullreq/pr_diff.go deleted file mode 100644 index bab0d403b..000000000 --- a/internal/api/controller/pullreq/pr_diff.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2022 Harness Inc. All rights reserved. -// Use of this source code is governed by the Polyform Free Trial License -// that can be found in the LICENSE.md file for this repository. - -package pullreq - -import ( - "context" - "fmt" - "io" - - "github.com/harness/gitness/gitrpc" - "github.com/harness/gitness/internal/auth" - "github.com/harness/gitness/types/enum" -) - -// RawDiff writes raw git diff to writer w. -func (c *Controller) RawDiff( - ctx context.Context, - session *auth.Session, - repoRef string, - pullreqNum int64, - setSHAs func(sourceSHA, mergeBaseSHA string), - w io.Writer, -) error { - repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView) - if err != nil { - return fmt.Errorf("failed to acquire access to target repo: %w", err) - } - - pr, err := c.pullreqStore.FindByNumber(ctx, repo.ID, pullreqNum) - if err != nil { - return fmt.Errorf("failed to get pull request by number: %w", err) - } - - headRef := pr.SourceSHA - baseRef := pr.MergeBaseSHA - - setSHAs(headRef, baseRef) - - return c.gitRPCClient.RawDiff(ctx, &gitrpc.DiffParams{ - ReadParams: gitrpc.CreateRPCReadParams(repo), - BaseRef: baseRef, - HeadRef: headRef, - MergeBase: true, - }, w) -} diff --git a/internal/api/controller/repo/create.go b/internal/api/controller/repo/create.go index 0175b9c80..c7f7a60cf 100644 --- a/internal/api/controller/repo/create.go +++ b/internal/api/controller/repo/create.go @@ -8,6 +8,7 @@ import ( "bytes" "context" "fmt" + "strconv" "strings" "time" @@ -34,7 +35,7 @@ var ( ) type CreateInput struct { - ParentID int64 `json:"parent_id"` + ParentRef string `json:"parent_ref"` UID string `json:"uid"` DefaultBranch string `json:"default_branch"` Description string `json:"description"` @@ -47,7 +48,8 @@ type CreateInput struct { // Create creates a new repository. func (c *Controller) Create(ctx context.Context, session *auth.Session, in *CreateInput) (*types.Repository, error) { - if err := c.checkAuthRepoCreation(ctx, session, in.ParentID); err != nil { + parentSpace, err := c.getSpaceCheckAuthRepoCreation(ctx, session, in.ParentRef) + if err != nil { return nil, err } @@ -64,7 +66,7 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error { // lock parent space path to ensure it doesn't get updated while we setup new repo var spacePath *types.Path - spacePath, err = c.pathStore.FindPrimaryWithLock(ctx, enum.PathTargetTypeSpace, in.ParentID) + spacePath, err = c.pathStore.FindPrimaryWithLock(ctx, enum.PathTargetTypeSpace, parentSpace.ID) if err != nil { return usererror.BadRequest("Parent not found'") } @@ -72,7 +74,7 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea now := time.Now().UnixMilli() repo = &types.Repository{ Version: 0, - ParentID: in.ParentID, + ParentID: parentSpace.ID, UID: in.UID, GitUID: gitRPCResp.UID, Path: paths.Concatinate(spacePath.Value, in.UID), @@ -123,10 +125,14 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea return repo, nil } -func (c *Controller) checkAuthRepoCreation(ctx context.Context, session *auth.Session, parentID int64) error { - space, err := c.spaceStore.Find(ctx, parentID) +func (c *Controller) getSpaceCheckAuthRepoCreation( + ctx context.Context, + session *auth.Session, + parentRef string, +) (*types.Space, error) { + space, err := c.spaceStore.FindByRef(ctx, parentRef) if err != nil { - return fmt.Errorf("parent space not found: %w", err) + return nil, fmt.Errorf("parent space not found: %w", err) } // create is a special case - check permission without specific resource @@ -138,14 +144,16 @@ func (c *Controller) checkAuthRepoCreation(ctx context.Context, session *auth.Se err = apiauth.Check(ctx, c.authorizer, session, scope, resource, enum.PermissionRepoEdit) if err != nil { - return fmt.Errorf("auth check failed: %w", err) + return nil, fmt.Errorf("auth check failed: %w", err) } - return nil + return space, nil } func (c *Controller) sanitizeCreateInput(in *CreateInput) error { - if in.ParentID <= 0 { + parentRefAsID, err := strconv.ParseInt(in.ParentRef, 10, 64) + + if (err == nil && parentRefAsID <= 0) || (len(strings.TrimSpace(in.ParentRef)) == 0) { return errRepositoryRequiresParent } diff --git a/internal/api/controller/repo/create_branch.go b/internal/api/controller/repo/create_branch.go index 9e335f7b1..0bc01ae45 100644 --- a/internal/api/controller/repo/create_branch.go +++ b/internal/api/controller/repo/create_branch.go @@ -22,7 +22,7 @@ type CreateBranchInput struct { // Target is the commit (or points to the commit) the new branch will be pointing to. // If no target is provided, the branch points to the same commit as the default branch of the repo. - Target *string `json:"target"` + Target string `json:"target"` } // CreateBranch creates a new branch for a repo. @@ -38,8 +38,8 @@ func (c *Controller) CreateBranch(ctx context.Context, session *auth.Session, } // set target to default branch in case no target was provided - if in.Target == nil || *in.Target == "" { - in.Target = &repo.DefaultBranch + if in.Target == "" { + in.Target = repo.DefaultBranch } err = checkBranchName(in.Name) @@ -55,7 +55,7 @@ func (c *Controller) CreateBranch(ctx context.Context, session *auth.Session, rpcOut, err := c.gitRPCClient.CreateBranch(ctx, &gitrpc.CreateBranchParams{ WriteParams: writeParams, BranchName: in.Name, - Target: *in.Target, + Target: in.Target, }) if err != nil { return nil, err diff --git a/internal/api/controller/repo/create_tag.go b/internal/api/controller/repo/create_commit_tag.go similarity index 54% rename from internal/api/controller/repo/create_tag.go rename to internal/api/controller/repo/create_commit_tag.go index ebb0d9cbf..ed474dcba 100644 --- a/internal/api/controller/repo/create_tag.go +++ b/internal/api/controller/repo/create_commit_tag.go @@ -7,6 +7,7 @@ package repo import ( "context" "fmt" + "time" "github.com/harness/gitness/gitrpc" apiauth "github.com/harness/gitness/internal/api/auth" @@ -14,18 +15,21 @@ import ( "github.com/harness/gitness/types/enum" ) -// CreateBranchInput used for branch creation apis. -type CreateTagInput struct { +// CreateCommitTagInput used for tag creation apis. +type CreateCommitTagInput struct { Name string `json:"name"` - // Target is the commit (or points to the commit) the new branch will be pointing to. - // If no target is provided, the branch points to the same commit as the default branch of the repo. - Target *string `json:"target"` - Message *string `json:"message"` + // Target is the commit (or points to the commit) the new tag will be pointing to. + // If no target is provided, the tag points to the same commit as the default branch of the repo. + Target string `json:"target"` + + // Message is the optional message the tag will be created with - if the message is empty + // the tag will be lightweight, otherwise it'll be annotated. + Message string `json:"message"` } -// CreateTag creates a new tag for a repo. -func (c *Controller) CreateTag(ctx context.Context, session *auth.Session, - repoRef string, in *CreateTagInput) (*CommitTag, error) { +// CreateCommitTag creates a new tag for a repo. +func (c *Controller) CreateCommitTag(ctx context.Context, session *auth.Session, + repoRef string, in *CreateCommitTagInput) (*CommitTag, error) { repo, err := c.repoStore.FindByRef(ctx, repoRef) if err != nil { return nil, err @@ -36,8 +40,8 @@ func (c *Controller) CreateTag(ctx context.Context, session *auth.Session, } // set target to default branch in case no branch or commit was provided - if in.Target == nil || *in.Target == "" { - in.Target = &repo.DefaultBranch + if in.Target == "" { + in.Target = repo.DefaultBranch } writeParams, err := CreateRPCWriteParams(ctx, c.urlProvider, session, repo) @@ -45,11 +49,14 @@ func (c *Controller) CreateTag(ctx context.Context, session *auth.Session, return nil, fmt.Errorf("failed to create RPC write params: %w", err) } - rpcOut, err := c.gitRPCClient.CreateTag(ctx, &gitrpc.CreateTagParams{ + now := time.Now() + rpcOut, err := c.gitRPCClient.CreateCommitTag(ctx, &gitrpc.CreateCommitTagParams{ WriteParams: writeParams, Name: in.Name, - SHA: *in.Target, - Message: *in.Message, + Target: in.Target, + Message: in.Message, + Tagger: rpcIdentityFromPrincipal(session.Principal), + TaggerDate: &now, }) if err != nil { diff --git a/internal/api/controller/repo/move.go b/internal/api/controller/repo/move.go index a4a87ab09..f0cdf8bf2 100644 --- a/internal/api/controller/repo/move.go +++ b/internal/api/controller/repo/move.go @@ -7,6 +7,8 @@ package repo import ( "context" "fmt" + "strconv" + "strings" "time" apiauth "github.com/harness/gitness/internal/api/auth" @@ -20,13 +22,34 @@ import ( // MoveInput is used for moving a repo. type MoveInput struct { UID *string `json:"uid"` - ParentID *int64 `json:"parent_id"` + ParentRef *string `json:"parent_ref"` KeepAsAlias bool `json:"keep_as_alias"` } func (i *MoveInput) hasChanges(repo *types.Repository) bool { - return (i.UID != nil && *i.UID != repo.UID) || - (i.ParentID != nil && *i.ParentID != repo.ParentID) + if i.UID != nil && *i.UID != repo.UID { + return true + } + + if i.ParentRef != nil { + parentRefAsID, err := strconv.ParseInt(*i.ParentRef, 10, 64) + // if parsing was successful, user provided actual space id + if err == nil && parentRefAsID != repo.ParentID { + return true + } + + // if parsing was unsuccessful, user provided input as path + if err != nil { + // repo is an existing entity, assume that path is not empty and thus no error + repoParentPath, _, _ := paths.DisectLeaf(repo.Path) + parentRefAsPath := *i.ParentRef + if parentRefAsPath != repoParentPath { + return true + } + } + } + + return false } // Move moves a repository to a new space and/or uid. @@ -40,15 +63,22 @@ func (c *Controller) Move(ctx context.Context, session *auth.Session, } permission := enum.PermissionRepoEdit - if in.ParentID != nil && *in.ParentID != repo.ParentID { + var inParentSpaceID *int64 + if in.ParentRef != nil { // ensure user has access to new space (parentId not sanitized!) - if err = c.checkAuthRepoCreation(ctx, session, *in.ParentID); err != nil { + inParentSpace, err := c.getSpaceCheckAuthRepoCreation(ctx, session, *in.ParentRef) + if err != nil { return nil, fmt.Errorf("failed to verify repo creation permissions on new parent space: %w", err) } - // TODO: what would be correct permissions on repo? (technically we are deleting it from the old space) - permission = enum.PermissionRepoDelete + inParentSpaceID = &inParentSpace.ID + + if inParentSpace.ID != repo.ParentID { + // TODO: what would be correct permissions on repo? (technically we are deleting it from the old space) + permission = enum.PermissionRepoDelete + } } + if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, permission, false); err != nil { return nil, err } @@ -66,8 +96,8 @@ func (c *Controller) Move(ctx context.Context, session *auth.Session, if in.UID != nil { r.UID = *in.UID } - if in.ParentID != nil { - r.ParentID = *in.ParentID + if inParentSpaceID != nil { + r.ParentID = *inParentSpaceID } return nil }) @@ -132,8 +162,9 @@ func (c *Controller) sanitizeMoveInput(in *MoveInput) error { } } - if in.ParentID != nil { - if *in.ParentID <= 0 { + if in.ParentRef != nil { + parentRefAsID, err := strconv.ParseInt(*in.ParentRef, 10, 64) + if (err == nil && parentRefAsID <= 0) || (len(strings.TrimSpace(*in.ParentRef)) == 0) { return errRepositoryRequiresParent } } diff --git a/internal/api/controller/serviceaccount/create_token.go b/internal/api/controller/serviceaccount/create_token.go index dfa3ebd4c..46636de00 100644 --- a/internal/api/controller/serviceaccount/create_token.go +++ b/internal/api/controller/serviceaccount/create_token.go @@ -18,7 +18,7 @@ import ( type CreateTokenInput struct { UID string `json:"uid"` - Lifetime time.Duration `json:"lifetime"` + Lifetime *time.Duration `json:"lifetime"` Grants enum.AccessGrant `json:"grants"` } @@ -33,7 +33,7 @@ func (c *Controller) CreateToken(ctx context.Context, session *auth.Session, if err = check.UID(in.UID); err != nil { return nil, err } - if err = check.TokenLifetime(in.Lifetime); err != nil { + if err = check.TokenLifetime(in.Lifetime, true); err != nil { return nil, err } // TODO: Added to unblock UI - Depending on product decision enforce grants, or remove Grants completely. diff --git a/internal/api/controller/space/controller.go b/internal/api/controller/space/controller.go index 96e55201e..25f1b626f 100644 --- a/internal/api/controller/space/controller.go +++ b/internal/api/controller/space/controller.go @@ -15,31 +15,34 @@ import ( ) type Controller struct { - db *sqlx.DB - urlProvider *url.Provider - uidCheck check.PathUID - authorizer authz.Authorizer - pathStore store.PathStore - spaceStore store.SpaceStore - repoStore store.RepoStore - principalStore store.PrincipalStore - repoCtrl *repo.Controller + db *sqlx.DB + urlProvider *url.Provider + uidCheck check.PathUID + authorizer authz.Authorizer + pathStore store.PathStore + spaceStore store.SpaceStore + repoStore store.RepoStore + principalStore store.PrincipalStore + repoCtrl *repo.Controller + membershipStore store.MembershipStore } func NewController(db *sqlx.DB, urlProvider *url.Provider, uidCheck check.PathUID, authorizer authz.Authorizer, pathStore store.PathStore, spaceStore store.SpaceStore, repoStore store.RepoStore, principalStore store.PrincipalStore, repoCtrl *repo.Controller, + membershipStore store.MembershipStore, ) *Controller { return &Controller{ - db: db, - urlProvider: urlProvider, - uidCheck: uidCheck, - authorizer: authorizer, - pathStore: pathStore, - spaceStore: spaceStore, - repoStore: repoStore, - principalStore: principalStore, - repoCtrl: repoCtrl, + db: db, + urlProvider: urlProvider, + uidCheck: uidCheck, + authorizer: authorizer, + pathStore: pathStore, + spaceStore: spaceStore, + repoStore: repoStore, + principalStore: principalStore, + repoCtrl: repoCtrl, + membershipStore: membershipStore, } } diff --git a/internal/api/controller/space/create.go b/internal/api/controller/space/create.go index c5d27777a..c5a3081ba 100644 --- a/internal/api/controller/space/create.go +++ b/internal/api/controller/space/create.go @@ -7,12 +7,14 @@ package space import ( "context" "fmt" + "strconv" "strings" "time" apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/internal/bootstrap" "github.com/harness/gitness/internal/paths" "github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/types" @@ -26,15 +28,18 @@ var ( ) type CreateInput struct { - ParentID int64 `json:"parent_id"` + ParentRef string `json:"parent_ref"` UID string `json:"uid"` Description string `json:"description"` IsPublic bool `json:"is_public"` } // Create creates a new space. +// +//nolint:gocognit // refactor if required func (c *Controller) Create(ctx context.Context, session *auth.Session, in *CreateInput) (*types.Space, error) { - if err := c.checkAuthSpaceCreation(ctx, session, in.ParentID); err != nil { + parentSpace, err := c.getSpaceCheckAuthSpaceCreation(ctx, session, in.ParentRef) + if err != nil { return nil, err } @@ -43,11 +48,13 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea } var space *types.Space - err := dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error { + err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error { spacePath := in.UID - if in.ParentID > 0 { + parentSpaceID := int64(0) + if parentSpace != nil { + parentSpaceID = parentSpace.ID // lock parent space path to ensure it doesn't get updated while we setup new space - parentPath, err := c.pathStore.FindPrimaryWithLock(ctx, enum.PathTargetTypeSpace, in.ParentID) + parentPath, err := c.pathStore.FindPrimaryWithLock(ctx, enum.PathTargetTypeSpace, parentSpaceID) if err != nil { return usererror.BadRequest("Parent not found") } @@ -63,7 +70,7 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea now := time.Now().UnixMilli() space = &types.Space{ Version: 0, - ParentID: in.ParentID, + ParentID: parentSpaceID, UID: in.UID, Path: spacePath, Description: in.Description, @@ -72,7 +79,7 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea Created: now, Updated: now, } - err := c.spaceStore.Create(ctx, space) + err = c.spaceStore.Create(ctx, space) if err != nil { return fmt.Errorf("space creation failed: %w", err) } @@ -84,14 +91,35 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea TargetType: enum.PathTargetTypeSpace, TargetID: space.ID, CreatedBy: space.CreatedBy, - Created: space.Created, - Updated: space.Updated, + Created: now, + Updated: now, } err = c.pathStore.Create(ctx, path) if err != nil { return fmt.Errorf("failed to create path: %w", err) } + // add space membership to top level space only (as the user doesn't have inherited permissions already) + parentRefAsID, err := strconv.ParseInt(in.ParentRef, 10, 64) + if (err == nil && parentRefAsID == 0) || (len(strings.TrimSpace(in.ParentRef)) == 0) { + membership := &types.Membership{ + MembershipKey: types.MembershipKey{ + SpaceID: space.ID, + PrincipalID: session.Principal.ID, + }, + Role: enum.MembershipRoleSpaceOwner, + + // membership has been created by the system + CreatedBy: bootstrap.NewSystemServiceSession().Principal.ID, + Created: now, + Updated: now, + } + err = c.membershipStore.Create(ctx, membership) + if err != nil { + return fmt.Errorf("failed to make user owner of the space: %w", err) + } + } + return nil }) if err != nil { @@ -101,19 +129,25 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea return space, nil } -func (c *Controller) checkAuthSpaceCreation(ctx context.Context, session *auth.Session, parentID int64) error { - if parentID <= 0 { +func (c *Controller) getSpaceCheckAuthSpaceCreation( + ctx context.Context, + session *auth.Session, + parentRef string, +) (*types.Space, error) { + parentRefAsID, err := strconv.ParseInt(parentRef, 10, 64) + if (parentRefAsID <= 0 && err == nil) || (len(strings.TrimSpace(parentRef)) == 0) { // TODO: Restrict top level space creation. if session == nil { - return usererror.ErrUnauthorized + return nil, usererror.ErrUnauthorized } - return nil + //nolint:nilnil + return nil, nil } - parentSpace, err := c.spaceStore.Find(ctx, parentID) + parentSpace, err := c.spaceStore.FindByRef(ctx, parentRef) if err != nil { - return fmt.Errorf("failed to get parent space: %w", err) + return nil, fmt.Errorf("failed to get parent space: %w", err) } // create is a special case - check permission without specific resource @@ -123,18 +157,25 @@ func (c *Controller) checkAuthSpaceCreation(ctx context.Context, session *auth.S Name: "", } if err = apiauth.Check(ctx, c.authorizer, session, scope, resource, enum.PermissionSpaceCreate); err != nil { - return err + return nil, err } - return nil + return parentSpace, nil } func (c *Controller) sanitizeCreateInput(in *CreateInput) error { - if in.ParentID < 0 { + parentRefAsID, err := strconv.ParseInt(in.ParentRef, 10, 64) + + if err == nil && parentRefAsID < 0 { return errParentIDNegative } - if err := c.uidCheck(in.UID, in.ParentID == 0); err != nil { + isRoot := false + if (err == nil && parentRefAsID == 0) || (len(strings.TrimSpace(in.ParentRef)) == 0) { + isRoot = true + } + + if err := c.uidCheck(in.UID, isRoot); err != nil { return err } diff --git a/internal/api/controller/space/delete.go b/internal/api/controller/space/delete.go index 10547d127..5a4e96dbd 100644 --- a/internal/api/controller/space/delete.go +++ b/internal/api/controller/space/delete.go @@ -36,7 +36,7 @@ func (c *Controller) Delete(ctx context.Context, session *auth.Session, spaceRef func (c *Controller) DeleteNoAuth(ctx context.Context, session *auth.Session, spaceID int64) error { filter := &types.SpaceFilter{ Page: 1, - Size: int(math.MaxInt), + Size: math.MaxInt, Query: "", Order: enum.OrderAsc, Sort: enum.SpaceAttrNone, diff --git a/internal/api/controller/space/list_spaces.go b/internal/api/controller/space/list_spaces.go index 2e2d7db83..e2f2d5ea5 100644 --- a/internal/api/controller/space/list_spaces.go +++ b/internal/api/controller/space/list_spaces.go @@ -10,13 +10,17 @@ import ( apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" ) // ListSpaces lists the child spaces of a space. -func (c *Controller) ListSpaces(ctx context.Context, session *auth.Session, - spaceRef string, filter *types.SpaceFilter) ([]*types.Space, int64, error) { +func (c *Controller) ListSpaces(ctx context.Context, + session *auth.Session, + spaceRef string, + filter *types.SpaceFilter, +) ([]types.Space, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, 0, err @@ -28,20 +32,30 @@ func (c *Controller) ListSpaces(ctx context.Context, session *auth.Session, return c.ListSpacesNoAuth(ctx, space.ID, filter) } -// List spaces WITHOUT checking PermissionSpaceView. +// ListSpacesNoAuth lists spaces WITHOUT checking PermissionSpaceView. func (c *Controller) ListSpacesNoAuth( ctx context.Context, spaceID int64, filter *types.SpaceFilter, -) ([]*types.Space, int64, error) { - count, err := c.spaceStore.Count(ctx, spaceID, filter) - if err != nil { - return nil, 0, fmt.Errorf("failed to count child spaces: %w", err) - } +) ([]types.Space, int64, error) { + var spaces []types.Space + var count int64 - spaces, err := c.spaceStore.List(ctx, spaceID, filter) + err := dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { + count, err = c.spaceStore.Count(ctx, spaceID, filter) + if err != nil { + return fmt.Errorf("failed to count child spaces: %w", err) + } + + spaces, err = c.spaceStore.List(ctx, spaceID, filter) + if err != nil { + return fmt.Errorf("failed to list child spaces: %w", err) + } + + return nil + }, dbtx.TxDefaultReadOnly) if err != nil { - return nil, 0, fmt.Errorf("failed to list child spaces: %w", err) + return nil, 0, err } /* diff --git a/internal/api/controller/space/membership_add.go b/internal/api/controller/space/membership_add.go new file mode 100644 index 000000000..0a9f452c8 --- /dev/null +++ b/internal/api/controller/space/membership_add.go @@ -0,0 +1,100 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package space + +import ( + "context" + "fmt" + "time" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/api/usererror" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/store" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" + + "github.com/pkg/errors" +) + +type MembershipAddInput struct { + UserUID string `json:"user_uid"` + Role enum.MembershipRole `json:"role"` +} + +func (in *MembershipAddInput) Validate() error { + if in.UserUID == "" { + return usererror.BadRequest("UserUID must be provided") + } + + if in.Role == "" { + return usererror.BadRequest("Role must be provided") + } + + role, ok := in.Role.Sanitize() + if !ok { + msg := fmt.Sprintf("Provided role '%s' is not suppored. Valid values are: %v", + in.Role, enum.MembershipRoles) + return usererror.BadRequest(msg) + } + + in.Role = role + + return nil +} + +// MembershipAdd adds a new membership to a space. +func (c *Controller) MembershipAdd(ctx context.Context, + session *auth.Session, + spaceRef string, + in *MembershipAddInput, +) (*types.MembershipUser, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, err + } + + if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit, false); err != nil { + return nil, err + } + + err = in.Validate() + if err != nil { + return nil, err + } + + user, err := c.principalStore.FindUserByUID(ctx, in.UserUID) + if errors.Is(err, store.ErrResourceNotFound) { + return nil, usererror.BadRequestf("User '%s' not found", in.UserUID) + } else if err != nil { + return nil, fmt.Errorf("failed to find the user: %w", err) + } + + now := time.Now().UnixMilli() + + membership := types.Membership{ + MembershipKey: types.MembershipKey{ + SpaceID: space.ID, + PrincipalID: user.ID, + }, + CreatedBy: session.Principal.ID, + Created: now, + Updated: now, + Role: in.Role, + } + + err = c.membershipStore.Create(ctx, &membership) + if err != nil { + return nil, fmt.Errorf("failed to create new membership: %w", err) + } + + result := &types.MembershipUser{ + Membership: membership, + Principal: *user.ToPrincipalInfo(), + AddedBy: *session.Principal.ToPrincipalInfo(), + } + + return result, nil +} diff --git a/internal/api/controller/space/membership_delete.go b/internal/api/controller/space/membership_delete.go new file mode 100644 index 000000000..0bdc8601d --- /dev/null +++ b/internal/api/controller/space/membership_delete.go @@ -0,0 +1,46 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package space + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// MembershipDelete removes an existing membership from a space. +func (c *Controller) MembershipDelete(ctx context.Context, + session *auth.Session, + spaceRef string, + userUID string, +) error { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return err + } + + if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit, false); err != nil { + return err + } + + user, err := c.principalStore.FindUserByUID(ctx, userUID) + if err != nil { + return fmt.Errorf("failed to find user by uid: %w", err) + } + + err = c.membershipStore.Delete(ctx, types.MembershipKey{ + SpaceID: space.ID, + PrincipalID: user.ID, + }) + if err != nil { + return fmt.Errorf("failed to delete user membership: %w", err) + } + + return nil +} diff --git a/internal/api/controller/space/membership_list.go b/internal/api/controller/space/membership_list.go new file mode 100644 index 000000000..07630d878 --- /dev/null +++ b/internal/api/controller/space/membership_list.go @@ -0,0 +1,54 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package space + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/store/database/dbtx" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// MembershipList lists all space memberships. +func (c *Controller) MembershipList(ctx context.Context, + session *auth.Session, + spaceRef string, + opts types.MembershipFilter, +) ([]types.MembershipUser, int64, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, 0, err + } + + if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, false); err != nil { + return nil, 0, err + } + + var memberships []types.MembershipUser + var membershipsCount int64 + + err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error { + memberships, err = c.membershipStore.ListUsers(ctx, space.ID, opts) + if err != nil { + return fmt.Errorf("failed to list memberships for space: %w", err) + } + + membershipsCount, err = c.membershipStore.CountUsers(ctx, space.ID, opts) + if err != nil { + return fmt.Errorf("failed to count memberships for space: %w", err) + } + + return nil + }, dbtx.TxDefaultReadOnly) + if err != nil { + return nil, 0, err + } + + return memberships, membershipsCount, nil +} diff --git a/internal/api/controller/space/membership_update.go b/internal/api/controller/space/membership_update.go new file mode 100644 index 000000000..4c86f8a4f --- /dev/null +++ b/internal/api/controller/space/membership_update.go @@ -0,0 +1,85 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package space + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/api/usererror" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +type MembershipUpdateInput struct { + Role enum.MembershipRole `json:"role"` +} + +func (in *MembershipUpdateInput) Validate() error { + if in.Role == "" { + return usererror.BadRequest("Role must be provided") + } + + role, ok := in.Role.Sanitize() + if !ok { + msg := fmt.Sprintf("Provided role '%s' is not suppored. Valid values are: %v", + in.Role, enum.MembershipRoles) + return usererror.BadRequest(msg) + } + + in.Role = role + + return nil +} + +// MembershipUpdate changes the role of an existing membership. +func (c *Controller) MembershipUpdate(ctx context.Context, + session *auth.Session, + spaceRef string, + userUID string, + in *MembershipUpdateInput, +) (*types.MembershipUser, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, err + } + + if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit, false); err != nil { + return nil, err + } + + err = in.Validate() + if err != nil { + return nil, err + } + + user, err := c.principalStore.FindUserByUID(ctx, userUID) + if err != nil { + return nil, fmt.Errorf("failed to find user by uid: %w", err) + } + + membership, err := c.membershipStore.FindUser(ctx, types.MembershipKey{ + SpaceID: space.ID, + PrincipalID: user.ID, + }) + if err != nil { + return nil, fmt.Errorf("failed to find membership for update: %w", err) + } + + if membership.Role == in.Role { + return membership, nil + } + + membership.Role = in.Role + + err = c.membershipStore.Update(ctx, &membership.Membership) + if err != nil { + return nil, fmt.Errorf("failed to update membership") + } + + return membership, nil +} diff --git a/internal/api/controller/space/move.go b/internal/api/controller/space/move.go index eb9cc6696..bc454a234 100644 --- a/internal/api/controller/space/move.go +++ b/internal/api/controller/space/move.go @@ -8,6 +8,7 @@ import ( "context" "errors" "fmt" + "strconv" "strings" "time" @@ -20,18 +21,41 @@ import ( "github.com/harness/gitness/types" "github.com/harness/gitness/types/check" "github.com/harness/gitness/types/enum" + + "github.com/gotidy/ptr" ) // MoveInput is used for moving a space. type MoveInput struct { UID *string `json:"uid"` - ParentID *int64 `json:"parent_id"` + ParentRef *string `json:"parent_ref"` KeepAsAlias bool `json:"keep_as_alias"` } func (i *MoveInput) hasChanges(space *types.Space) bool { - return (i.UID != nil && *i.UID != space.UID) || - (i.ParentID != nil && *i.ParentID != space.ParentID) + if i.UID != nil && *i.UID != space.UID { + return true + } + + if i.ParentRef != nil { + parentRefAsID, err := strconv.ParseInt(*i.ParentRef, 10, 64) + // if parsing was successful, user provided actual space id + if err == nil && parentRefAsID != space.ParentID { + return true + } + + // if parsing was unsuccessful, user provided input as path + if err != nil { + // space is an existing entity, assume that path is not empty and thus no error + spaceParentPath, _, _ := paths.DisectLeaf(space.Path) + parentRefAsPath := *i.ParentRef + if parentRefAsPath != spaceParentPath { + return true + } + } + } + + return false } // Move moves a space to a new space and/or name. @@ -44,16 +68,27 @@ func (c *Controller) Move(ctx context.Context, session *auth.Session, return nil, err } + var inParentSpaceID *int64 permission := enum.PermissionSpaceEdit - if in.ParentID != nil && *in.ParentID != space.ParentID { + if in.ParentRef != nil { // ensure user has access to new space (parentId not sanitized!) - if err = c.checkAuthSpaceCreation(ctx, session, *in.ParentID); err != nil { + inParentSpace, err := c.getSpaceCheckAuthSpaceCreation(ctx, session, *in.ParentRef) + if err != nil { return nil, fmt.Errorf("failed to verify space creation permissions on new parent space: %w", err) } - // TODO: what would be correct permissions on space? (technically we are deleting it from the old space) - permission = enum.PermissionSpaceDelete + if inParentSpace != nil { + inParentSpaceID = &inParentSpace.ID + } else { + inParentSpaceID = ptr.Int64(0) + } + + if *inParentSpaceID != space.ParentID { + // TODO: what would be correct permissions on space? (technically we are deleting it from the old space) + permission = enum.PermissionSpaceDelete + } } + if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, permission, false); err != nil { return nil, err } @@ -71,9 +106,11 @@ func (c *Controller) Move(ctx context.Context, session *auth.Session, if in.UID != nil { s.UID = *in.UID } - if in.ParentID != nil { - s.ParentID = *in.ParentID + + if inParentSpaceID != nil { + s.ParentID = *inParentSpaceID } + return nil }) if err != nil { @@ -121,11 +158,16 @@ func (c *Controller) Move(ctx context.Context, session *auth.Session, } func (c *Controller) sanitizeMoveInput(in *MoveInput, isRoot bool) error { - if in.ParentID != nil { - if *in.ParentID < 0 { + if in.ParentRef != nil { + parentRefAsID, err := strconv.ParseInt(*in.ParentRef, 10, 64) + + if err == nil && parentRefAsID < 0 { return errParentIDNegative } - isRoot = *in.ParentID == 0 + + if (err == nil && parentRefAsID == 0) || (len(strings.TrimSpace(*in.ParentRef)) == 0) { + isRoot = true + } } if in.UID != nil { diff --git a/internal/api/controller/space/wire.go b/internal/api/controller/space/wire.go index f49648337..d8b9cbbba 100644 --- a/internal/api/controller/space/wire.go +++ b/internal/api/controller/space/wire.go @@ -22,6 +22,11 @@ var WireSet = wire.NewSet( func ProvideController(db *sqlx.DB, urlProvider *url.Provider, uidCheck check.PathUID, authorizer authz.Authorizer, pathStore store.PathStore, spaceStore store.SpaceStore, repoStore store.RepoStore, - principalStore store.PrincipalStore, repoCtrl *repo.Controller) *Controller { - return NewController(db, urlProvider, uidCheck, authorizer, pathStore, spaceStore, repoStore, principalStore, repoCtrl) + principalStore store.PrincipalStore, repoCtrl *repo.Controller, + membershipStore store.MembershipStore, +) *Controller { + return NewController(db, urlProvider, uidCheck, authorizer, + pathStore, spaceStore, repoStore, + principalStore, repoCtrl, + membershipStore) } diff --git a/internal/api/controller/user/controller.go b/internal/api/controller/user/controller.go index 97d22b126..76060c3c1 100644 --- a/internal/api/controller/user/controller.go +++ b/internal/api/controller/user/controller.go @@ -21,29 +21,36 @@ type Controller struct { authorizer authz.Authorizer principalStore store.PrincipalStore tokenStore store.TokenStore - config *types.Config + membershipStore store.MembershipStore } -func NewController(principalUIDCheck check.PrincipalUID, authorizer authz.Authorizer, - principalStore store.PrincipalStore, tokenStore store.TokenStore, config *types.Config) *Controller { +func NewController( + principalUIDCheck check.PrincipalUID, + authorizer authz.Authorizer, + principalStore store.PrincipalStore, + tokenStore store.TokenStore, + membershipStore store.MembershipStore, +) *Controller { return &Controller{ principalUIDCheck: principalUIDCheck, authorizer: authorizer, principalStore: principalStore, tokenStore: tokenStore, - config: config, + membershipStore: membershipStore, } } var hashPassword = bcrypt.GenerateFromPassword func findUserFromUID(ctx context.Context, - principalStore store.PrincipalStore, userUID string) (*types.User, error) { + principalStore store.PrincipalStore, userUID string, +) (*types.User, error) { return principalStore.FindUserByUID(ctx, userUID) } func findUserFromEmail(ctx context.Context, - principalStore store.PrincipalStore, email string) (*types.User, error) { + principalStore store.PrincipalStore, email string, +) (*types.User, error) { return principalStore.FindUserByEmail(ctx, email) } diff --git a/internal/api/controller/user/create.go b/internal/api/controller/user/create.go index 0a9bdb559..fde9cf1c2 100644 --- a/internal/api/controller/user/create.go +++ b/internal/api/controller/user/create.go @@ -75,7 +75,7 @@ func (c *Controller) CreateNoAuth(ctx context.Context, in *CreateInput, admin bo return nil, err } - uCount, err := c.principalStore.CountUsers(ctx) + uCount, err := c.principalStore.CountUsers(ctx, &types.UserFilter{}) if err != nil { return nil, err } diff --git a/internal/api/controller/user/create_access_token.go b/internal/api/controller/user/create_access_token.go index 2d69762b3..5738452f4 100644 --- a/internal/api/controller/user/create_access_token.go +++ b/internal/api/controller/user/create_access_token.go @@ -18,7 +18,7 @@ import ( type CreateTokenInput struct { UID string `json:"uid"` - Lifetime time.Duration `json:"lifetime"` + Lifetime *time.Duration `json:"lifetime"` Grants enum.AccessGrant `json:"grants"` } @@ -40,7 +40,7 @@ func (c *Controller) CreateAccessToken(ctx context.Context, session *auth.Sessio if err = check.UID(in.UID); err != nil { return nil, err } - if err = check.TokenLifetime(in.Lifetime); err != nil { + if err = check.TokenLifetime(in.Lifetime, true); err != nil { return nil, err } // TODO: Added to unblock UI - Depending on product decision enforce grants, or remove Grants completely. diff --git a/internal/api/controller/user/delete.go b/internal/api/controller/user/delete.go index 654e73b07..483110e62 100644 --- a/internal/api/controller/user/delete.go +++ b/internal/api/controller/user/delete.go @@ -9,7 +9,9 @@ import ( "fmt" apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" ) @@ -21,6 +23,18 @@ func (c *Controller) Delete(ctx context.Context, session *auth.Session, return err } + // Fail if the user being deleted is the only admin in DB + if user.Admin { + admUsrCount, err := c.principalStore.CountUsers(ctx, &types.UserFilter{Admin: true}) + if err != nil { + return fmt.Errorf("failed to check admin user count: %w", err) + } + + if admUsrCount == 1 { + return usererror.BadRequest("cannot delete the only admin user") + } + } + // Ensure principal has required permissions on parent if err = apiauth.CheckUser(ctx, c.authorizer, session, user, enum.PermissionUserDelete); err != nil { return err diff --git a/internal/api/controller/user/list.go b/internal/api/controller/user/list.go index b7b3890d1..cbe6a5caa 100644 --- a/internal/api/controller/user/list.go +++ b/internal/api/controller/user/list.go @@ -28,7 +28,7 @@ func (c *Controller) List(ctx context.Context, session *auth.Session, return nil, 0, err } - count, err := c.principalStore.CountUsers(ctx) + count, err := c.principalStore.CountUsers(ctx, filter) if err != nil { return nil, 0, fmt.Errorf("failed to count users: %w", err) } diff --git a/internal/api/controller/user/membership_spaces.go b/internal/api/controller/user/membership_spaces.go new file mode 100644 index 000000000..a954e45d7 --- /dev/null +++ b/internal/api/controller/user/membership_spaces.go @@ -0,0 +1,38 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package user + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// MembershipSpaces lists all spaces in which the user is a member. +func (c *Controller) MembershipSpaces(ctx context.Context, + session *auth.Session, + userUID string, +) ([]types.MembershipSpace, error) { + user, err := findUserFromUID(ctx, c.principalStore, userUID) + if err != nil { + return nil, fmt.Errorf("failed to find user by UID: %w", err) + } + + // Ensure principal has required permissions. + if err = apiauth.CheckUser(ctx, c.authorizer, session, user, enum.PermissionUserView); err != nil { + return nil, err + } + + membershipSpaces, err := c.membershipStore.ListSpaces(ctx, user.ID) + if err != nil { + return nil, fmt.Errorf("failed to list membership spaces for user: %w", err) + } + + return membershipSpaces, nil +} diff --git a/internal/api/controller/user/wire.go b/internal/api/controller/user/wire.go index d565f3084..bf8418dbf 100644 --- a/internal/api/controller/user/wire.go +++ b/internal/api/controller/user/wire.go @@ -7,7 +7,6 @@ package user import ( "github.com/harness/gitness/internal/auth/authz" "github.com/harness/gitness/internal/store" - "github.com/harness/gitness/types" "github.com/harness/gitness/types/check" "github.com/google/wire" @@ -18,7 +17,17 @@ var WireSet = wire.NewSet( NewController, ) -func ProvideController(principalUIDCheck check.PrincipalUID, authorizer authz.Authorizer, - principalStore store.PrincipalStore, tokenStore store.TokenStore, config *types.Config) *Controller { - return NewController(principalUIDCheck, authorizer, principalStore, tokenStore, config) +func ProvideController( + principalUIDCheck check.PrincipalUID, + authorizer authz.Authorizer, + principalStore store.PrincipalStore, + tokenStore store.TokenStore, + membershipStore store.MembershipStore, +) *Controller { + return NewController( + principalUIDCheck, + authorizer, + principalStore, + tokenStore, + membershipStore) } diff --git a/internal/api/handler/repo/create_tag.go b/internal/api/handler/repo/create_commit_tag.go similarity index 90% rename from internal/api/handler/repo/create_tag.go rename to internal/api/handler/repo/create_commit_tag.go index 3cfc57764..7dbea64b7 100644 --- a/internal/api/handler/repo/create_tag.go +++ b/internal/api/handler/repo/create_commit_tag.go @@ -23,14 +23,14 @@ func HandleCreateCommitTag(repoCtrl *repo.Controller) http.HandlerFunc { return } - in := new(repo.CreateTagInput) + in := new(repo.CreateCommitTagInput) err = json.NewDecoder(r.Body).Decode(in) if err != nil { render.BadRequestf(w, "Invalid request body: %s.", err) return } - tag, err := repoCtrl.CreateTag(ctx, session, repoRef, in) + tag, err := repoCtrl.CreateCommitTag(ctx, session, repoRef, in) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/space/delete.go b/internal/api/handler/space/delete.go index c93b2ead4..acad17621 100644 --- a/internal/api/handler/space/delete.go +++ b/internal/api/handler/space/delete.go @@ -7,16 +7,13 @@ package space import ( "net/http" - "github.com/harness/gitness/internal/api/controller/repo" "github.com/harness/gitness/internal/api/controller/space" "github.com/harness/gitness/internal/api/render" "github.com/harness/gitness/internal/api/request" ) -/* - * Deletes a space. - */ -func HandleDelete(spaceCtrl *space.Controller, repoCtrl *repo.Controller) http.HandlerFunc { +// HandleDelete handles the delete space HTTP API. +func HandleDelete(spaceCtrl *space.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() session, _ := request.AuthSessionFrom(ctx) diff --git a/internal/api/handler/space/membership_add.go b/internal/api/handler/space/membership_add.go new file mode 100644 index 000000000..48368cf71 --- /dev/null +++ b/internal/api/handler/space/membership_add.go @@ -0,0 +1,43 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package space + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/space" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +// HandleMembershipAdd handles API that adds a new membership to a space. +func HandleMembershipAdd(spaceCtrl *space.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + spaceRef, err := request.GetSpaceRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + in := new(space.MembershipAddInput) + err = json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(w, "Invalid Request Body: %s.", err) + return + } + + memberInfo, err := spaceCtrl.MembershipAdd(ctx, session, spaceRef, in) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusCreated, memberInfo) + } +} diff --git a/internal/api/handler/pullreq/pr_diff.go b/internal/api/handler/space/membership_delete.go similarity index 52% rename from internal/api/handler/pullreq/pr_diff.go rename to internal/api/handler/space/membership_delete.go index 86e80e0e1..eed309be6 100644 --- a/internal/api/handler/pullreq/pr_diff.go +++ b/internal/api/handler/space/membership_delete.go @@ -2,44 +2,40 @@ // Use of this source code is governed by the Polyform Free Trial License // that can be found in the LICENSE.md file for this repository. -package pullreq +package space import ( "net/http" - "github.com/harness/gitness/internal/api/controller/pullreq" + "github.com/harness/gitness/internal/api/controller/space" "github.com/harness/gitness/internal/api/render" "github.com/harness/gitness/internal/api/request" ) -// HandleRawDiff returns raw git diff for PR. -func HandleRawDiff(pullreqCtrl *pullreq.Controller) http.HandlerFunc { +// HandleMembershipDelete handles API that deletes an existing space membership. +func HandleMembershipDelete(spaceCtrl *space.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() session, _ := request.AuthSessionFrom(ctx) - repoRef, err := request.GetRepoRefFromPath(r) + spaceRef, err := request.GetSpaceRefFromPath(r) if err != nil { render.TranslatedUserError(w, err) return } - pullreqNumber, err := request.GetPullReqNumberFromPath(r) + userUID, err := request.GetUserUIDFromPath(r) if err != nil { render.TranslatedUserError(w, err) return } - setSHAs := func(sourceSHA, mergeBaseSHA string) { - w.Header().Set("X-Source-Sha", sourceSHA) - w.Header().Set("X-Merge-Base-Sha", mergeBaseSHA) - } - - if err = pullreqCtrl.RawDiff(ctx, session, repoRef, pullreqNumber, setSHAs, w); err != nil { + err = spaceCtrl.MembershipDelete(ctx, session, spaceRef, userUID) + if err != nil { render.TranslatedUserError(w, err) return } - w.WriteHeader(http.StatusOK) + render.DeleteSuccessful(w) } } diff --git a/internal/api/handler/space/membership_list.go b/internal/api/handler/space/membership_list.go new file mode 100644 index 000000000..715cd48d6 --- /dev/null +++ b/internal/api/handler/space/membership_list.go @@ -0,0 +1,38 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package space + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/space" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +// HandleMembershipList handles API that lists all memberships of a space. +func HandleMembershipList(spaceCtrl *space.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + spaceRef, err := request.GetSpaceRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + filter := request.ParseMembershipFilter(r) + + memberships, membershipsCount, err := spaceCtrl.MembershipList(ctx, session, spaceRef, filter) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.Pagination(r, w, filter.Page, filter.Size, int(membershipsCount)) + render.JSON(w, http.StatusOK, memberships) + } +} diff --git a/internal/api/handler/space/membership_update.go b/internal/api/handler/space/membership_update.go new file mode 100644 index 000000000..ed00f112f --- /dev/null +++ b/internal/api/handler/space/membership_update.go @@ -0,0 +1,49 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package space + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/space" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +// HandleMembershipUpdate handles API that changes the role of an existing space membership. +func HandleMembershipUpdate(spaceCtrl *space.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + spaceRef, err := request.GetSpaceRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + userUID, err := request.GetUserUIDFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + in := new(space.MembershipUpdateInput) + err = json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(w, "Invalid Request Body: %s.", err) + return + } + + memberInfo, err := spaceCtrl.MembershipUpdate(ctx, session, spaceRef, userUID, in) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, memberInfo) + } +} diff --git a/internal/api/handler/user/membership_spaces.go b/internal/api/handler/user/membership_spaces.go new file mode 100644 index 000000000..b869f4f3d --- /dev/null +++ b/internal/api/handler/user/membership_spaces.go @@ -0,0 +1,29 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package user + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/user" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +func HandleMembershipSpaces(userCtrl *user.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + userUID := session.Principal.UID + + membershipSpaces, err := userCtrl.MembershipSpaces(ctx, session, userUID) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, membershipSpaces) + } +} diff --git a/internal/api/openapi/pullreq.go b/internal/api/openapi/pullreq.go index bd7c42207..fdff3d37f 100644 --- a/internal/api/openapi/pullreq.go +++ b/internal/api/openapi/pullreq.go @@ -459,17 +459,6 @@ func pullReqOperations(reflector *openapi3.Reflector) { _ = reflector.SetJSONResponse(&opListCommits, new(usererror.Error), http.StatusNotFound) _ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/pullreq/{pullreq_number}/commits", opListCommits) - opRawDiff := openapi3.Operation{} - opRawDiff.WithTags("pullreq") - opRawDiff.WithMapOfAnything(map[string]interface{}{"operationId": "rawPullReqDiff"}) - _ = reflector.SetRequest(&opRawDiff, new(pullReqRequest), http.MethodGet) - _ = reflector.SetStringResponse(&opRawDiff, http.StatusOK, "text/plain") - _ = reflector.SetJSONResponse(&opRawDiff, new(usererror.Error), http.StatusInternalServerError) - _ = reflector.SetJSONResponse(&opRawDiff, new(usererror.Error), http.StatusUnauthorized) - _ = reflector.SetJSONResponse(&opRawDiff, new(usererror.Error), http.StatusForbidden) - _ = reflector.SetJSONResponse(&opRawDiff, new(usererror.Error), http.StatusNotFound) - _ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/pullreq/{pullreq_number}/diff", opRawDiff) - opMetaData := openapi3.Operation{} opMetaData.WithTags("pullreq") opMetaData.WithMapOfAnything(map[string]interface{}{"operationId": "pullReqMetaData"}) diff --git a/internal/api/openapi/repo.go b/internal/api/openapi/repo.go index 6f6010050..6d1d0a042 100644 --- a/internal/api/openapi/repo.go +++ b/internal/api/openapi/repo.go @@ -134,7 +134,7 @@ type deleteBranchRequest struct { type createTagRequest struct { repoRequest - repo.CreateTagInput + repo.CreateCommitTagInput } type listTagsRequest struct { diff --git a/internal/api/openapi/space.go b/internal/api/openapi/space.go index 9e5726b4e..0a95c73de 100644 --- a/internal/api/openapi/space.go +++ b/internal/api/openapi/space.go @@ -244,4 +244,60 @@ func spaceOperations(reflector *openapi3.Reflector) { _ = reflector.SetJSONResponse(&onDeletePath, new(usererror.Error), http.StatusForbidden) _ = reflector.SetJSONResponse(&onDeletePath, new(usererror.Error), http.StatusNotFound) _ = reflector.Spec.AddOperation(http.MethodDelete, "/spaces/{space_ref}/paths/{path_id}", onDeletePath) + + opMembershipAdd := openapi3.Operation{} + opMembershipAdd.WithTags("space") + opMembershipAdd.WithMapOfAnything(map[string]interface{}{"operationId": "membershipAdd"}) + _ = reflector.SetRequest(&opMembershipAdd, struct { + spaceRequest + space.MembershipAddInput + }{}, http.MethodPost) + _ = reflector.SetJSONResponse(&opMembershipAdd, &types.Membership{}, http.StatusCreated) + _ = reflector.SetJSONResponse(&opMembershipAdd, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opMembershipAdd, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opMembershipAdd, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opMembershipAdd, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodPost, "/spaces/{space_ref}/members", opMembershipAdd) + + opMembershipDelete := openapi3.Operation{} + opMembershipDelete.WithTags("space") + opMembershipDelete.WithMapOfAnything(map[string]interface{}{"operationId": "membershipDelete"}) + _ = reflector.SetRequest(&opMembershipDelete, struct { + spaceRequest + UserUID string `path:"user_uid"` + }{}, http.MethodDelete) + _ = reflector.SetJSONResponse(&opMembershipDelete, nil, http.StatusNoContent) + _ = reflector.SetJSONResponse(&opMembershipDelete, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opMembershipDelete, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opMembershipDelete, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opMembershipDelete, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodDelete, "/spaces/{space_ref}/members/{user_uid}", opMembershipDelete) + + opMembershipUpdate := openapi3.Operation{} + opMembershipUpdate.WithTags("space") + opMembershipUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "membershipUpdate"}) + _ = reflector.SetRequest(&opMembershipUpdate, &struct { + spaceRequest + UserUID string `path:"user_uid"` + space.MembershipUpdateInput + }{}, http.MethodPatch) + _ = reflector.SetJSONResponse(&opMembershipUpdate, &types.Membership{}, http.StatusOK) + _ = reflector.SetJSONResponse(&opMembershipUpdate, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opMembershipUpdate, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opMembershipUpdate, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opMembershipUpdate, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodPatch, "/spaces/{space_ref}/members/{user_uid}", opMembershipUpdate) + + opMembershipList := openapi3.Operation{} + opMembershipList.WithTags("space") + opMembershipList.WithMapOfAnything(map[string]interface{}{"operationId": "membershipList"}) + _ = reflector.SetRequest(&opMembershipList, &struct { + spaceRequest + }{}, http.MethodGet) + _ = reflector.SetJSONResponse(&opMembershipList, []types.Membership{}, http.StatusOK) + _ = reflector.SetJSONResponse(&opMembershipList, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opMembershipList, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opMembershipList, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opMembershipList, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/members", opMembershipList) } diff --git a/internal/api/openapi/user.go b/internal/api/openapi/user.go index 0e7c1c32e..b7003efea 100644 --- a/internal/api/openapi/user.go +++ b/internal/api/openapi/user.go @@ -44,4 +44,12 @@ func buildUser(reflector *openapi3.Reflector) { _ = reflector.SetJSONResponse(&opToken, new(types.TokenResponse), http.StatusCreated) _ = reflector.SetJSONResponse(&opToken, new(usererror.Error), http.StatusInternalServerError) _ = reflector.Spec.AddOperation(http.MethodPost, "/user/token", opToken) + + opMemberSpaces := openapi3.Operation{} + opMemberSpaces.WithTags("user") + opMemberSpaces.WithMapOfAnything(map[string]interface{}{"operationId": "membershipSpaces"}) + _ = reflector.SetRequest(&opMemberSpaces, struct{}{}, http.MethodGet) + _ = reflector.SetJSONResponse(&opMemberSpaces, new([]types.MembershipSpace), http.StatusOK) + _ = reflector.SetJSONResponse(&opMemberSpaces, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.Spec.AddOperation(http.MethodGet, "/user/memberships", opMemberSpaces) } diff --git a/internal/api/request/membership.go b/internal/api/request/membership.go new file mode 100644 index 000000000..4aed70a67 --- /dev/null +++ b/internal/api/request/membership.go @@ -0,0 +1,30 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package request + +import ( + "net/http" + + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// ParseMembershipSort extracts the membership sort parameter from the url. +func ParseMembershipSort(r *http.Request) enum.MembershipSort { + return enum.ParseMembershipSort( + r.URL.Query().Get(QueryParamSort), + ) +} + +// ParseMembershipFilter extracts the membership filter from the url. +func ParseMembershipFilter(r *http.Request) types.MembershipFilter { + return types.MembershipFilter{ + Page: ParsePage(r), + Size: ParseLimit(r), + Query: ParseQuery(r), + Sort: ParseMembershipSort(r), + Order: ParseOrder(r), + } +} diff --git a/internal/auth/authz/membership.go b/internal/auth/authz/membership.go new file mode 100644 index 000000000..0bcfc4f19 --- /dev/null +++ b/internal/auth/authz/membership.go @@ -0,0 +1,99 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package authz + +import ( + "context" + + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/internal/paths" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" + + "github.com/rs/zerolog/log" +) + +var _ Authorizer = (*MembershipAuthorizer)(nil) + +type MembershipAuthorizer struct { + permissionCache PermissionCache +} + +func NewMembershipAuthorizer( + permissionCache PermissionCache, +) *MembershipAuthorizer { + return &MembershipAuthorizer{ + permissionCache: permissionCache, + } +} + +func (a *MembershipAuthorizer) Check( + ctx context.Context, + session *auth.Session, + scope *types.Scope, + resource *types.Resource, + permission enum.Permission, +) (bool, error) { + log.Ctx(ctx).Debug().Msgf( + "[MembershipAuthorizer] %s with id '%d' requests %s for %s '%s' in scope %#v with metadata %#v", + session.Principal.Type, + session.Principal.ID, + permission, + resource.Type, + resource.Name, + scope, + session.Metadata, + ) + + if session.Principal.Admin { + return true, nil // system admin can call any API + } + + var spaceRef string + + switch resource.Type { + case enum.ResourceTypeSpace: + spaceRef = paths.Concatinate(scope.SpacePath, resource.Name) + + case enum.ResourceTypeRepo: + spaceRef = scope.SpacePath + + case enum.ResourceTypeServiceAccount: + spaceRef = scope.SpacePath + + case enum.ResourceTypeUser: + // a user is allowed to view / edit themselves + if resource.Name == session.Principal.UID && + (permission == enum.PermissionUserView || permission == enum.PermissionUserEdit) { + return true, nil + } + + // everything else is reserved for admins only (like operations on users other than yourself, or setting admin) + return false, nil + + // Service operations aren't exposed to users + case enum.ResourceTypeService: + return false, nil + + default: + return false, nil + } + + return a.permissionCache.Get(ctx, PermissionCacheKey{ + PrincipalID: session.Principal.ID, + SpaceRef: spaceRef, + Permission: permission, + }) +} +func (a *MembershipAuthorizer) CheckAll(ctx context.Context, session *auth.Session, + permissionChecks ...types.PermissionCheck) (bool, error) { + for _, p := range permissionChecks { + if _, err := a.Check(ctx, session, &p.Scope, &p.Resource, p.Permission); err != nil { + return false, err + } + } + + return true, nil +} diff --git a/internal/auth/authz/membership_cache.go b/internal/auth/authz/membership_cache.go new file mode 100644 index 000000000..b920c5b04 --- /dev/null +++ b/internal/auth/authz/membership_cache.go @@ -0,0 +1,91 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package authz + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/harness/gitness/cache" + "github.com/harness/gitness/internal/paths" + "github.com/harness/gitness/internal/store" + gitness_store "github.com/harness/gitness/store" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" + + "golang.org/x/exp/slices" +) + +type PermissionCacheKey struct { + PrincipalID int64 + SpaceRef string + Permission enum.Permission +} +type PermissionCache cache.Cache[PermissionCacheKey, bool] + +func NewPermissionCache( + spaceStore store.SpaceStore, + membershipStore store.MembershipStore, + cacheDuration time.Duration, +) PermissionCache { + return cache.New[PermissionCacheKey, bool](permissionCacheGetter{ + spaceStore: spaceStore, + membershipStore: membershipStore, + }, cacheDuration) +} + +type permissionCacheGetter struct { + spaceStore store.SpaceStore + membershipStore store.MembershipStore +} + +func (g permissionCacheGetter) Find(ctx context.Context, key PermissionCacheKey) (bool, error) { + spaceRef := key.SpaceRef + principalID := key.PrincipalID + + // Find the starting space. + space, err := g.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return false, fmt.Errorf("failed to find space '%s': %w", spaceRef, err) + } + + // limit the depth to be safe (e.g. root/space1/space2 => maxDepth of 3) + maxDepth := len(paths.Segments(spaceRef)) + + for depth := 0; depth < maxDepth; depth++ { + // Find the membership in the current space. + membership, err := g.membershipStore.Find(ctx, types.MembershipKey{ + SpaceID: space.ID, + PrincipalID: principalID, + }) + if err != nil && !errors.Is(err, gitness_store.ErrResourceNotFound) { + return false, fmt.Errorf("failed to find membership: %w", err) + } + + // If the membership is defined in the current space, check if the user has the required permission. + if membership != nil { + _, hasRole := slices.BinarySearch(membership.Role.Permissions(), key.Permission) + if hasRole { + return true, nil + } + } + + // If membership with the requested permission has not been found in the current space, + // move to the parent space, if any. + + if space.ParentID == 0 { + return false, nil + } + + space, err = g.spaceStore.Find(ctx, space.ParentID) + if err != nil { + return false, fmt.Errorf("failed to find parent space with id %d: %w", space.ParentID, err) + } + } + + return false, nil +} diff --git a/internal/auth/authz/wire.go b/internal/auth/authz/wire.go index 1e55f48a4..81cd2fc46 100644 --- a/internal/auth/authz/wire.go +++ b/internal/auth/authz/wire.go @@ -5,14 +5,27 @@ package authz import ( + "time" + + "github.com/harness/gitness/internal/store" + "github.com/google/wire" ) // WireSet provides a wire set for this package. var WireSet = wire.NewSet( ProvideAuthorizer, + ProvidePermissionCache, ) -func ProvideAuthorizer() Authorizer { - return NewUnsafeAuthorizer() +func ProvideAuthorizer(pCache PermissionCache) Authorizer { + return NewMembershipAuthorizer(pCache) +} + +func ProvidePermissionCache( + spaceStore store.SpaceStore, + membershipStore store.MembershipStore, +) PermissionCache { + const permissionCacheTimeout = time.Second * 15 + return NewPermissionCache(spaceStore, membershipStore, permissionCacheTimeout) } diff --git a/internal/router/api.go b/internal/router/api.go index 7b4d1f202..b491ed5d1 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -125,7 +125,7 @@ func setupRoutesV1(r chi.Router, checkCtrl *check.Controller, sysCtrl *system.Controller, ) { - setupSpaces(r, spaceCtrl, repoCtrl) + setupSpaces(r, spaceCtrl) setupRepos(r, repoCtrl, pullreqCtrl, webhookCtrl, checkCtrl) setupUser(r, userCtrl) setupServiceAccounts(r, saCtrl) @@ -137,7 +137,7 @@ func setupRoutesV1(r chi.Router, setupResources(r) } -func setupSpaces(r chi.Router, spaceCtrl *space.Controller, repoCtrl *repo.Controller) { +func setupSpaces(r chi.Router, spaceCtrl *space.Controller) { r.Route("/spaces", func(r chi.Router) { // Create takes path and parentId via body, not uri r.Post("/", handlerspace.HandleCreate(spaceCtrl)) @@ -146,7 +146,7 @@ func setupSpaces(r chi.Router, spaceCtrl *space.Controller, repoCtrl *repo.Contr // space operations r.Get("/", handlerspace.HandleFind(spaceCtrl)) r.Patch("/", handlerspace.HandleUpdate(spaceCtrl)) - r.Delete("/", handlerspace.HandleDelete(spaceCtrl, repoCtrl)) + r.Delete("/", handlerspace.HandleDelete(spaceCtrl)) r.Post("/move", handlerspace.HandleMove(spaceCtrl)) r.Get("/spaces", handlerspace.HandleListSpaces(spaceCtrl)) @@ -163,6 +163,15 @@ func setupSpaces(r chi.Router, spaceCtrl *space.Controller, repoCtrl *repo.Contr r.Delete("/", handlerspace.HandleDeletePath(spaceCtrl)) }) }) + + r.Route("/members", func(r chi.Router) { + r.Get("/", handlerspace.HandleMembershipList(spaceCtrl)) + r.Post("/", handlerspace.HandleMembershipAdd(spaceCtrl)) + r.Route(fmt.Sprintf("/{%s}", request.PathParamUserUID), func(r chi.Router) { + r.Delete("/", handlerspace.HandleMembershipDelete(spaceCtrl)) + r.Patch("/", handlerspace.HandleMembershipUpdate(spaceCtrl)) + }) + }) }) }) } @@ -304,7 +313,6 @@ func SetupPullReq(r chi.Router, pullreqCtrl *pullreq.Controller) { r.Post("/", handlerpullreq.HandleReviewSubmit(pullreqCtrl)) }) r.Post("/merge", handlerpullreq.HandleMerge(pullreqCtrl)) - r.Get("/diff", handlerpullreq.HandleRawDiff(pullreqCtrl)) r.Get("/commits", handlerpullreq.HandleCommits(pullreqCtrl)) r.Get("/metadata", handlerpullreq.HandleMetadata(pullreqCtrl)) }) @@ -348,6 +356,7 @@ func setupUser(r chi.Router, userCtrl *user.Controller) { r.Use(middlewareprincipal.RestrictTo(enum.PrincipalTypeUser)) r.Get("/", handleruser.HandleFind(userCtrl)) r.Patch("/", handleruser.HandleUpdate(userCtrl)) + r.Get("/memberships", handleruser.HandleMembershipSpaces(userCtrl)) // PAT r.Route("/tokens", func(r chi.Router) { diff --git a/internal/services/pullreq/handlers_branch.go b/internal/services/pullreq/handlers_branch.go index 3dceee943..89b97f404 100644 --- a/internal/services/pullreq/handlers_branch.go +++ b/internal/services/pullreq/handlers_branch.go @@ -24,6 +24,21 @@ import ( func (s *Service) triggerPREventOnBranchUpdate(ctx context.Context, event *events.Event[*gitevents.BranchUpdatedPayload], ) error { + // we should always update PR mergeable status check when target branch is updated. + // - main + // |- develop + // |- feature1 + // |- feature2 + // when feature2 merge changes into develop branch then feature1 branch is not consistent anymore + // and need to run mergeable check even nothing was changed on feature1, same applies to main if someone + // push new commit to main then develop should merge status should be unchecked. + if branch, err := getBranchFromRef(event.Payload.Ref); err == nil { + err = s.pullreqStore.UpdateMergeCheckStatus(ctx, event.Payload.RepoID, branch, enum.MergeCheckStatusUnchecked) + if err != nil { + return err + } + } + // TODO: This function is currently executed directly on branch update event. // TODO: But it should be executed after the PR's head ref has been updated. // TODO: This is to make sure the commit exists on the target repository for forked repositories. @@ -148,17 +163,11 @@ func (s *Service) forEveryOpenPR(ctx context.Context, repoID int64, ref string, fn func(pr *types.PullReq) error, ) { - const refPrefix = "refs/heads/" const largeLimit = 1000000 - if !strings.HasPrefix(ref, refPrefix) { - log.Ctx(ctx).Error().Msg("failed to get branch name from branch ref") - return - } - - branch := ref[len(refPrefix):] + branch, err := getBranchFromRef(ref) if len(branch) == 0 { - log.Ctx(ctx).Error().Msg("got an empty branch name from branch ref") + log.Ctx(ctx).Err(err).Send() return } @@ -182,3 +191,16 @@ func (s *Service) forEveryOpenPR(ctx context.Context, } } } + +func getBranchFromRef(ref string) (string, error) { + const refPrefix = "refs/heads/" + if !strings.HasPrefix(ref, refPrefix) { + return "", fmt.Errorf("failed to get branch name from branch ref %s", ref) + } + + branch := ref[len(refPrefix):] + if len(branch) == 0 { + return "", fmt.Errorf("got an empty branch name from branch ref %s", ref) + } + return branch, nil +} diff --git a/internal/store/database.go b/internal/store/database.go index b59200dc5..fafd43224 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -60,8 +60,8 @@ type ( // ListUsers returns a list of users. ListUsers(ctx context.Context, params *types.UserFilter) ([]*types.User, error) - // CountUsers returns a count of users. - CountUsers(ctx context.Context) (int64, error) + // CountUsers returns a count of users which match the given filter. + CountUsers(ctx context.Context, opts *types.UserFilter) (int64, error) /* * SERVICE ACCOUNT RELATED OPERATIONS. @@ -187,7 +187,7 @@ type ( Count(ctx context.Context, id int64, opts *types.SpaceFilter) (int64, error) // List returns a list of child spaces in a space. - List(ctx context.Context, id int64, opts *types.SpaceFilter) ([]*types.Space, error) + List(ctx context.Context, id int64, opts *types.SpaceFilter) ([]types.Space, error) } // RepoStore defines the repository data storage. @@ -223,6 +223,18 @@ type ( Find(ctx context.Context, id int64) (*types.RepositoryGitInfo, error) } + // MembershipStore defines the membership data storage. + MembershipStore interface { + Find(ctx context.Context, key types.MembershipKey) (*types.Membership, error) + FindUser(ctx context.Context, key types.MembershipKey) (*types.MembershipUser, error) + Create(ctx context.Context, membership *types.Membership) error + Update(ctx context.Context, membership *types.Membership) error + Delete(ctx context.Context, key types.MembershipKey) error + CountUsers(ctx context.Context, spaceID int64, filter types.MembershipFilter) (int64, error) + ListUsers(ctx context.Context, spaceID int64, filter types.MembershipFilter) ([]types.MembershipUser, error) + ListSpaces(ctx context.Context, userID int64) ([]types.MembershipSpace, error) + } + // TokenStore defines the token data storage. TokenStore interface { // Find finds the token by id @@ -273,6 +285,9 @@ type ( // It will set new values to the ActivitySeq, Version and Updated fields. UpdateActivitySeq(ctx context.Context, pr *types.PullReq) (*types.PullReq, error) + // Update all PR where target branch points to new SHA + UpdateMergeCheckStatus(ctx context.Context, targetRepo int64, targetBranch string, status enum.MergeCheckStatus) error + // Delete the pull request. Delete(ctx context.Context, id int64) error diff --git a/internal/store/database/membership.go b/internal/store/database/membership.go new file mode 100644 index 000000000..4aaf54ebc --- /dev/null +++ b/internal/store/database/membership.go @@ -0,0 +1,409 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package database + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/harness/gitness/internal/store" + "github.com/harness/gitness/store/database" + "github.com/harness/gitness/store/database/dbtx" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" + + "github.com/Masterminds/squirrel" + "github.com/jmoiron/sqlx" +) + +var _ store.MembershipStore = (*MembershipStore)(nil) + +// NewMembershipStore returns a new MembershipStore. +func NewMembershipStore(db *sqlx.DB, pCache store.PrincipalInfoCache) *MembershipStore { + return &MembershipStore{ + db: db, + pCache: pCache, + } +} + +// MembershipStore implements store.MembershipStore backed by a relational database. +type MembershipStore struct { + db *sqlx.DB + pCache store.PrincipalInfoCache +} + +type membership struct { + SpaceID int64 `db:"membership_space_id"` + PrincipalID int64 `db:"membership_principal_id"` + + CreatedBy int64 `db:"membership_created_by"` + Created int64 `db:"membership_created"` + Updated int64 `db:"membership_updated"` + + Role enum.MembershipRole `db:"membership_role"` +} + +type membershipPrincipal struct { + membership + principalInfo +} + +type membershipSpace struct { + membership + space +} + +const ( + membershipColumns = ` + membership_space_id + ,membership_principal_id + ,membership_created_by + ,membership_created + ,membership_updated + ,membership_role` + + membershipSelectBase = ` + SELECT` + membershipColumns + ` + FROM memberships` +) + +// Find finds the membership by space id and principal id. +func (s *MembershipStore) Find(ctx context.Context, key types.MembershipKey) (*types.Membership, error) { + const sqlQuery = membershipSelectBase + ` + WHERE membership_space_id = $1 AND membership_principal_id = $2` + + db := dbtx.GetAccessor(ctx, s.db) + + dst := &membership{} + if err := db.GetContext(ctx, dst, sqlQuery, key.SpaceID, key.PrincipalID); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to find membership") + } + + result := mapToMembership(dst) + + return &result, nil +} + +func (s *MembershipStore) FindUser(ctx context.Context, key types.MembershipKey) (*types.MembershipUser, error) { + m, err := s.Find(ctx, key) + if err != nil { + return nil, err + } + + result, err := s.addPrincipalInfos(ctx, m) + if err != nil { + return nil, err + } + + return &result, nil +} + +// Create creates a new membership. +func (s *MembershipStore) Create(ctx context.Context, membership *types.Membership) error { + const sqlQuery = ` + INSERT INTO memberships ( + membership_space_id + ,membership_principal_id + ,membership_created_by + ,membership_created + ,membership_updated + ,membership_role + ) values ( + :membership_space_id + ,:membership_principal_id + ,:membership_created_by + ,:membership_created + ,:membership_updated + ,:membership_role + )` + + db := dbtx.GetAccessor(ctx, s.db) + + query, arg, err := db.BindNamed(sqlQuery, mapToInternalMembership(membership)) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to bind membership object") + } + + if _, err = db.ExecContext(ctx, query, arg...); err != nil { + return database.ProcessSQLErrorf(err, "Failed to insert membership") + } + + return nil +} + +// Update updates the role of a member of a space. +func (s *MembershipStore) Update(ctx context.Context, membership *types.Membership) error { + const sqlQuery = ` + UPDATE memberships + SET + membership_updated = :membership_updated + ,membership_role = :membership_role + WHERE membership_space_id = :membership_space_id AND + membership_principal_id = :membership_principal_id` + + db := dbtx.GetAccessor(ctx, s.db) + + dbMembership := mapToInternalMembership(membership) + dbMembership.Updated = time.Now().UnixMilli() + + query, arg, err := db.BindNamed(sqlQuery, dbMembership) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to bind membership object") + } + + _, err = db.ExecContext(ctx, query, arg...) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to update membership role") + } + + membership.Updated = dbMembership.Updated + + return nil +} + +// Delete deletes the membership. +func (s *MembershipStore) Delete(ctx context.Context, key types.MembershipKey) error { + const sqlQuery = ` + DELETE from memberships + WHERE membership_space_id = $1 AND + membership_principal_id = $2` + + db := dbtx.GetAccessor(ctx, s.db) + + if _, err := db.ExecContext(ctx, sqlQuery, key.SpaceID, key.PrincipalID); err != nil { + return database.ProcessSQLErrorf(err, "delete membership query failed") + } + return nil +} + +// CountUsers returns a number of users memberships that matches the provided filter. +func (s *MembershipStore) CountUsers(ctx context.Context, + spaceID int64, + filter types.MembershipFilter, +) (int64, error) { + stmt := database.Builder. + Select("count(*)"). + From("memberships"). + InnerJoin("principals ON membership_principal_id = principal_id"). + Where("membership_space_id = ?", spaceID) + + stmt = prepareMembershipListUsersStmt(stmt, filter) + + sql, args, err := stmt.ToSql() + if err != nil { + return 0, fmt.Errorf("failed to convert membership count query to sql: %w", err) + } + + db := dbtx.GetAccessor(ctx, s.db) + + var count int64 + err = db.QueryRowContext(ctx, sql, args...).Scan(&count) + if err != nil { + return 0, database.ProcessSQLErrorf(err, "Failed executing membership count query") + } + + return count, nil +} + +// ListUsers returns a list of memberships for a space or a user. +func (s *MembershipStore) ListUsers(ctx context.Context, + spaceID int64, + filter types.MembershipFilter, +) ([]types.MembershipUser, error) { + const columns = membershipColumns + "," + principalInfoCommonColumns + stmt := database.Builder. + Select(columns). + From("memberships"). + InnerJoin("principals ON membership_principal_id = principal_id"). + Where("membership_space_id = ?", spaceID) + + stmt = prepareMembershipListUsersStmt(stmt, filter) + stmt = stmt.Limit(database.Limit(filter.Size)) + stmt = stmt.Offset(database.Offset(filter.Page, filter.Size)) + + order := filter.Order + if order == enum.OrderDefault { + order = enum.OrderAsc + } + + switch filter.Sort { + case enum.MembershipSortName: + stmt = stmt.OrderBy("principal_display_name " + order.String()) + case enum.MembershipSortCreated: + stmt = stmt.OrderBy("membership_created " + order.String()) + case enum.MembershipSortNone: + } + + sql, args, err := stmt.ToSql() + if err != nil { + return nil, fmt.Errorf("failed to convert membership users list query to sql: %w", err) + } + + dst := make([]*membershipPrincipal, 0) + + db := dbtx.GetAccessor(ctx, s.db) + + if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed executing membership users list query") + } + + result, err := s.mapToMembershipUsers(ctx, dst) + if err != nil { + return nil, fmt.Errorf("failed to map memberships users to external type: %w", err) + } + + return result, nil +} + +func prepareMembershipListUsersStmt( + stmt squirrel.SelectBuilder, + opts types.MembershipFilter, +) squirrel.SelectBuilder { + if opts.Query != "" { + searchTerm := "%%" + strings.ToLower(opts.Query) + "%%" + stmt = stmt.Where("LOWER(principal_display_name) LIKE ?", searchTerm) + } + + return stmt +} + +// ListSpaces returns a list of spaces in which the provided user is a member. +func (s *MembershipStore) ListSpaces(ctx context.Context, + userID int64, +) ([]types.MembershipSpace, error) { + const columns = membershipColumns + "," + spaceColumnsForJoin + stmt := database.Builder. + Select(columns). + From("memberships"). + InnerJoin("spaces ON spaces.space_id = membership_space_id"). + InnerJoin(`paths ON spaces.space_id=paths.path_space_id AND paths.path_is_primary=true`). + Where("membership_principal_id = ?", userID). + OrderBy("space_path asc") + + sql, args, err := stmt.ToSql() + if err != nil { + return nil, fmt.Errorf("failed to convert membership spaces list query to sql: %w", err) + } + + db := dbtx.GetAccessor(ctx, s.db) + + dst := make([]*membershipSpace, 0) + if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed executing custom list query") + } + + result, err := s.mapToMembershipSpaces(ctx, dst) + if err != nil { + return nil, fmt.Errorf("failed to map memberships spaces to external type: %w", err) + } + + return result, nil +} + +func mapToMembership(m *membership) types.Membership { + return types.Membership{ + MembershipKey: types.MembershipKey{ + SpaceID: m.SpaceID, + PrincipalID: m.PrincipalID, + }, + CreatedBy: m.CreatedBy, + Created: m.Created, + Updated: m.Updated, + Role: m.Role, + } +} + +func mapToInternalMembership(m *types.Membership) membership { + return membership{ + SpaceID: m.SpaceID, + PrincipalID: m.PrincipalID, + CreatedBy: m.CreatedBy, + Created: m.Created, + Updated: m.Updated, + Role: m.Role, + } +} + +func (s *MembershipStore) addPrincipalInfos(ctx context.Context, m *types.Membership) (types.MembershipUser, error) { + var result types.MembershipUser + + // pull principal infos from cache + infoMap, err := s.pCache.Map(ctx, []int64{m.CreatedBy, m.PrincipalID}) + if err != nil { + return result, fmt.Errorf("failed to load membership principal infos: %w", err) + } + + if user, ok := infoMap[m.PrincipalID]; ok { + result.Principal = *user + } else { + return result, fmt.Errorf("failed to find membership principal info: %w", err) + } + + if addedBy, ok := infoMap[m.CreatedBy]; ok { + result.AddedBy = *addedBy + } + + result.Membership = *m + + return result, nil +} + +func (s *MembershipStore) mapToMembershipUsers(ctx context.Context, + ms []*membershipPrincipal, +) ([]types.MembershipUser, error) { + // collect all principal IDs + ids := make([]int64, 0, len(ms)) + for _, m := range ms { + ids = append(ids, m.membership.CreatedBy) + } + + // pull principal infos from cache + infoMap, err := s.pCache.Map(ctx, ids) + if err != nil { + return nil, fmt.Errorf("failed to load membership principal infos: %w", err) + } + + // attach the principal infos back to the slice items + res := make([]types.MembershipUser, len(ms)) + for i, m := range ms { + res[i].Membership = mapToMembership(&m.membership) + res[i].Principal = mapToPrincipalInfo(&m.principalInfo) + if addedBy, ok := infoMap[m.membership.CreatedBy]; ok { + res[i].AddedBy = *addedBy + } + } + + return res, nil +} + +func (s *MembershipStore) mapToMembershipSpaces(ctx context.Context, + ms []*membershipSpace, +) ([]types.MembershipSpace, error) { + // collect all principal IDs + ids := make([]int64, 0, len(ms)) + for _, m := range ms { + ids = append(ids, m.membership.CreatedBy) + } + + // pull principal infos from cache + infoMap, err := s.pCache.Map(ctx, ids) + if err != nil { + return nil, fmt.Errorf("failed to load membership principal infos: %w", err) + } + + // attach the principal infos back to the slice items + res := make([]types.MembershipSpace, len(ms)) + for i, m := range ms { + res[i].Membership = mapToMembership(&m.membership) + res[i].Space = mapToSpace(&m.space) + if addedBy, ok := infoMap[m.membership.CreatedBy]; ok { + res[i].AddedBy = *addedBy + } + } + + return res, nil +} diff --git a/internal/store/database/migrate/postgres/0019_create_table_memberships.down.sql b/internal/store/database/migrate/postgres/0019_create_table_memberships.down.sql new file mode 100644 index 000000000..cb23bba6b --- /dev/null +++ b/internal/store/database/migrate/postgres/0019_create_table_memberships.down.sql @@ -0,0 +1,2 @@ +DROP TABLE memberships; + diff --git a/internal/store/database/migrate/postgres/0019_create_table_memberships.up.sql b/internal/store/database/migrate/postgres/0019_create_table_memberships.up.sql new file mode 100644 index 000000000..e0b9ef5b7 --- /dev/null +++ b/internal/store/database/migrate/postgres/0019_create_table_memberships.up.sql @@ -0,0 +1,21 @@ +CREATE TABLE memberships ( + membership_space_id INTEGER NOT NULL +,membership_principal_id INTEGER NOT NULL +,membership_created_by INTEGER NOT NULL +,membership_created BIGINT NOT NULL +,membership_updated BIGINT NOT NULL +,membership_role TEXT NOT NULL +,CONSTRAINT pk_memberships PRIMARY KEY (membership_space_id, membership_principal_id) +,CONSTRAINT fk_membership_space_id FOREIGN KEY (membership_space_id) + REFERENCES spaces (space_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +,CONSTRAINT fk_membership_principal_id FOREIGN KEY (membership_principal_id) + REFERENCES principals (principal_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +,CONSTRAINT fk_membership_created_by FOREIGN KEY (membership_created_by) + REFERENCES principals (principal_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION +); diff --git a/internal/store/database/migrate/sqlite/0019_create_table_memberships.down.sql b/internal/store/database/migrate/sqlite/0019_create_table_memberships.down.sql new file mode 100644 index 000000000..cb23bba6b --- /dev/null +++ b/internal/store/database/migrate/sqlite/0019_create_table_memberships.down.sql @@ -0,0 +1,2 @@ +DROP TABLE memberships; + diff --git a/internal/store/database/migrate/sqlite/0019_create_table_memberships.up.sql b/internal/store/database/migrate/sqlite/0019_create_table_memberships.up.sql new file mode 100644 index 000000000..e0b9ef5b7 --- /dev/null +++ b/internal/store/database/migrate/sqlite/0019_create_table_memberships.up.sql @@ -0,0 +1,21 @@ +CREATE TABLE memberships ( + membership_space_id INTEGER NOT NULL +,membership_principal_id INTEGER NOT NULL +,membership_created_by INTEGER NOT NULL +,membership_created BIGINT NOT NULL +,membership_updated BIGINT NOT NULL +,membership_role TEXT NOT NULL +,CONSTRAINT pk_memberships PRIMARY KEY (membership_space_id, membership_principal_id) +,CONSTRAINT fk_membership_space_id FOREIGN KEY (membership_space_id) + REFERENCES spaces (space_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +,CONSTRAINT fk_membership_principal_id FOREIGN KEY (membership_principal_id) + REFERENCES principals (principal_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +,CONSTRAINT fk_membership_created_by FOREIGN KEY (membership_created_by) + REFERENCES principals (principal_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION +); diff --git a/internal/store/database/principal_info.go b/internal/store/database/principal_info.go index a882e4598..4b9e5281e 100644 --- a/internal/store/database/principal_info.go +++ b/internal/store/database/principal_info.go @@ -11,6 +11,7 @@ import ( "github.com/harness/gitness/store/database" "github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" "github.com/Masterminds/squirrel" "github.com/jmoiron/sqlx" @@ -32,15 +33,25 @@ type PrincipalInfoView struct { const ( principalInfoCommonColumns = ` - principal_id, - principal_uid, - principal_email, - principal_display_name, - principal_type, - principal_created, - principal_updated` + principal_id + ,principal_uid + ,principal_email + ,principal_display_name + ,principal_type + ,principal_created + ,principal_updated` ) +type principalInfo struct { + ID int64 `db:"principal_id"` + UID string `db:"principal_uid"` + DisplayName string `db:"principal_display_name"` + Email string `db:"principal_email"` + Type enum.PrincipalType `db:"principal_type"` + Created int64 `db:"principal_created"` + Updated int64 `db:"principal_updated"` +} + // Find returns a single principal info object by id from the `principals` database table. func (s *PrincipalInfoView) Find(ctx context.Context, id int64) (*types.PrincipalInfo, error) { const sqlQuery = ` @@ -107,3 +118,15 @@ func (s *PrincipalInfoView) FindMany(ctx context.Context, ids []int64) ([]*types return result, nil } + +func mapToPrincipalInfo(p *principalInfo) types.PrincipalInfo { + return types.PrincipalInfo{ + ID: p.ID, + UID: p.UID, + DisplayName: p.DisplayName, + Email: p.Email, + Type: p.Type, + Created: p.Created, + Updated: p.Updated, + } +} diff --git a/internal/store/database/principal_test.go b/internal/store/database/principal_test.go index c80cd15c4..5bb030cb0 100644 --- a/internal/store/database/principal_test.go +++ b/internal/store/database/principal_test.go @@ -104,7 +104,7 @@ func testUserDuplicate(store store.PrincipalStore) func(t *testing.T) { // and compares to the expected count. func testUserCount(store store.PrincipalStore) func(t *testing.T) { return func(t *testing.T) { - got, err := store.CountUsers(context.Background()) + got, err := store.CountUsers(context.Background(), &types.UserFilter{}) if err != nil { t.Error(err) return diff --git a/internal/store/database/principal_user.go b/internal/store/database/principal_user.go index 356215b76..de0177ff7 100644 --- a/internal/store/database/principal_user.go +++ b/internal/store/database/principal_user.go @@ -224,17 +224,26 @@ func (s *PrincipalStore) ListUsers(ctx context.Context, opts *types.UserFilter) return s.mapDBUsers(dst), nil } -// CountUsers returns a count of users. -func (s *PrincipalStore) CountUsers(ctx context.Context) (int64, error) { - const sqlQuery = ` - SELECT count(*) - FROM principals - WHERE principal_type = 'user'` +// CountUsers returns a count of users matching the given filter. +func (s *PrincipalStore) CountUsers(ctx context.Context, opts *types.UserFilter) (int64, error) { + stmt := database.Builder. + Select("count(*)"). + From("principals"). + Where("principal_type = 'user'") + + if opts.Admin { + stmt = stmt.Where("principal_admin = ?", opts.Admin) + } + + sql, args, err := stmt.ToSql() + if err != nil { + return 0, errors.Wrap(err, "Failed to convert query to sql") + } db := dbtx.GetAccessor(ctx, s.db) var count int64 - err := db.QueryRowContext(ctx, sqlQuery).Scan(&count) + err = db.QueryRowContext(ctx, sql, args...).Scan(&count) if err != nil { return 0, database.ProcessSQLErrorf(err, "Failed executing count query") } diff --git a/internal/store/database/pullreq.go b/internal/store/database/pullreq.go index 8ad4bbd44..dd95d9050 100644 --- a/internal/store/database/pullreq.go +++ b/internal/store/database/pullreq.go @@ -335,6 +335,36 @@ func (s *PullReqStore) UpdateActivitySeq(ctx context.Context, pr *types.PullReq) }) } +// UpdateMergeCheckStatus updates the pull request's mergeability status +// for all pr which target branch points to targetBranch. +func (s *PullReqStore) UpdateMergeCheckStatus( + ctx context.Context, + targetRepo int64, + targetBranch string, + status enum.MergeCheckStatus, +) error { + const query = ` + UPDATE pullreqs + SET + pullreq_updated = $1 + ,pullreq_merge_check_status = $2 + WHERE pullreq_target_repo_id = $3 AND + pullreq_target_branch = $4 AND + pullreq_state not in ($5, $6)` + + db := dbtx.GetAccessor(ctx, s.db) + + updatedAt := time.Now() + + _, err := db.ExecContext(ctx, query, updatedAt, status, targetRepo, targetBranch, + enum.PullReqStateClosed, enum.PullReqStateClosed) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to update mergeable status check %s in pull requests", status) + } + + return nil +} + // Delete the pull request. func (s *PullReqStore) Delete(ctx context.Context, id int64) error { const pullReqDelete = `DELETE FROM pullreqs WHERE pullreq_id = $1` diff --git a/internal/store/database/space.go b/internal/store/database/space.go index 904ca0f2e..36395aa41 100644 --- a/internal/store/database/space.go +++ b/internal/store/database/space.go @@ -86,7 +86,9 @@ func (s *SpaceStore) Find(ctx context.Context, id int64) (*types.Space, error) { return nil, database.ProcessSQLErrorf(err, "Failed to find space") } - return mapToSpace(dst), nil + result := mapToSpace(dst) + + return &result, nil } // FindByRef finds the space using the spaceRef as either the id or the space path. @@ -113,6 +115,10 @@ func (s *SpaceStore) FindByRef(ctx context.Context, spaceRef string) (*types.Spa // Create a new space. func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error { + if space == nil { + return errors.New("space is nil") + } + const sqlQuery = ` INSERT INTO spaces ( space_version @@ -134,14 +140,9 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error { ,:space_updated ) RETURNING space_id` - dbSpace, err := mapToInternalSpace(space) - if err != nil { - return fmt.Errorf("failed to map space: %w", err) - } - db := dbtx.GetAccessor(ctx, s.db) - query, args, err := db.BindNamed(sqlQuery, dbSpace) + query, args, err := db.BindNamed(sqlQuery, mapToInternalSpace(space)) if err != nil { return database.ProcessSQLErrorf(err, "Failed to bind space object") } @@ -153,8 +154,12 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error { return nil } -// Updates the space details. +// Update updates the space details. func (s *SpaceStore) Update(ctx context.Context, space *types.Space) error { + if space == nil { + return errors.New("space is nil") + } + const sqlQuery = ` UPDATE spaces SET @@ -166,10 +171,7 @@ func (s *SpaceStore) Update(ctx context.Context, space *types.Space) error { ,space_is_public = :space_is_public WHERE space_id = :space_id AND space_version = :space_version - 1` - dbSpace, err := mapToInternalSpace(space) - if err != nil { - return fmt.Errorf("failed to map space: %w", err) - } + dbSpace := mapToInternalSpace(space) // update Version (used for optimistic locking) and Updated time dbSpace.Version++ @@ -205,7 +207,8 @@ func (s *SpaceStore) Update(ctx context.Context, space *types.Space) error { // UpdateOptLock updates the space using the optimistic locking mechanism. func (s *SpaceStore) UpdateOptLock(ctx context.Context, space *types.Space, - mutateFn func(space *types.Space) error) (*types.Space, error) { + mutateFn func(space *types.Space) error, +) (*types.Space, error) { for { dup := *space @@ -229,7 +232,7 @@ func (s *SpaceStore) UpdateOptLock(ctx context.Context, } } -// Deletes the space. +// Delete deletes a space. func (s *SpaceStore) Delete(ctx context.Context, id int64) error { const sqlQuery = ` DELETE FROM spaces @@ -271,7 +274,7 @@ func (s *SpaceStore) Count(ctx context.Context, id int64, opts *types.SpaceFilte } // List returns a list of spaces under the parent space. -func (s *SpaceStore) List(ctx context.Context, id int64, opts *types.SpaceFilter) ([]*types.Space, error) { +func (s *SpaceStore) List(ctx context.Context, id int64, opts *types.SpaceFilter) ([]types.Space, error) { stmt := database.Builder. Select(spaceColumnsForJoin). From("spaces"). @@ -310,7 +313,7 @@ func (s *SpaceStore) List(ctx context.Context, id int64, opts *types.SpaceFilter db := dbtx.GetAccessor(ctx, s.db) - dst := []*space{} + var dst []*space if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { return nil, database.ProcessSQLErrorf(err, "Failed executing custom list query") } @@ -318,8 +321,8 @@ func (s *SpaceStore) List(ctx context.Context, id int64, opts *types.SpaceFilter return mapToSpaces(dst), nil } -func mapToSpace(s *space) *types.Space { - res := &types.Space{ +func mapToSpace(s *space) types.Space { + res := types.Space{ ID: s.ID, Version: s.Version, UID: s.UID, @@ -339,21 +342,16 @@ func mapToSpace(s *space) *types.Space { return res } -func mapToSpaces(spaces []*space) []*types.Space { - res := make([]*types.Space, len(spaces)) +func mapToSpaces(spaces []*space) []types.Space { + res := make([]types.Space, len(spaces)) for i := range spaces { res[i] = mapToSpace(spaces[i]) } return res } -func mapToInternalSpace(s *types.Space) (*space, error) { - // space comes from outside. - if s == nil { - return nil, fmt.Errorf("space is nil") - } - - res := &space{ +func mapToInternalSpace(s *types.Space) space { + res := space{ ID: s.ID, Version: s.Version, UID: s.UID, @@ -371,5 +369,5 @@ func mapToInternalSpace(s *types.Space) (*space, error) { res.ParentID = null.IntFrom(s.ParentID) } - return res, nil + return res } diff --git a/internal/store/database/wire.go b/internal/store/database/wire.go index 4870f3d98..ff6a66870 100644 --- a/internal/store/database/wire.go +++ b/internal/store/database/wire.go @@ -24,6 +24,7 @@ var WireSet = wire.NewSet( ProvideSpaceStore, ProvideRepoStore, ProvideRepoGitInfoView, + ProvideMembershipStore, ProvideTokenStore, ProvidePullReqStore, ProvidePullReqActivityStore, @@ -36,7 +37,7 @@ var WireSet = wire.NewSet( ProvideReqCheckStore, ) -// helper function to setup the database by performing automated +// migrator is helper function to set up the database by performing automated // database migration steps. func migrator(ctx context.Context, db *sqlx.DB) error { return migrate.Migrate(ctx, db) @@ -82,6 +83,13 @@ func ProvideRepoGitInfoView(db *sqlx.DB) store.RepoGitInfoView { return NewRepoGitInfoView(db) } +func ProvideMembershipStore( + db *sqlx.DB, + principalInfoCache store.PrincipalInfoCache, +) store.MembershipStore { + return NewMembershipStore(db, principalInfoCache) +} + // ProvideTokenStore provides a token store. func ProvideTokenStore(db *sqlx.DB) store.TokenStore { return NewTokenStore(db) @@ -89,13 +97,15 @@ func ProvideTokenStore(db *sqlx.DB) store.TokenStore { // ProvidePullReqStore provides a pull request store. func ProvidePullReqStore(db *sqlx.DB, - principalInfoCache store.PrincipalInfoCache) store.PullReqStore { + principalInfoCache store.PrincipalInfoCache, +) store.PullReqStore { return NewPullReqStore(db, principalInfoCache) } // ProvidePullReqActivityStore provides a pull request activity store. func ProvidePullReqActivityStore(db *sqlx.DB, - principalInfoCache store.PrincipalInfoCache) store.PullReqActivityStore { + principalInfoCache store.PrincipalInfoCache, +) store.PullReqActivityStore { return NewPullReqActivityStore(db, principalInfoCache) } @@ -111,7 +121,8 @@ func ProvidePullReqReviewStore(db *sqlx.DB) store.PullReqReviewStore { // ProvidePullReqReviewerStore provides a pull request reviewer store. func ProvidePullReqReviewerStore(db *sqlx.DB, - principalInfoCache store.PrincipalInfoCache) store.PullReqReviewerStore { + principalInfoCache store.PrincipalInfoCache, +) store.PullReqReviewerStore { return NewPullReqReviewerStore(db, principalInfoCache) } diff --git a/internal/token/jwt.go b/internal/token/jwt.go index 86e465350..db49243a3 100644 --- a/internal/token/jwt.go +++ b/internal/token/jwt.go @@ -27,12 +27,17 @@ type JWTClaims struct { // GenerateJWTForToken generates a jwt for a given token. func GenerateJWTForToken(token *types.Token, secret string) (string, error) { + var expiresAt int64 + if token.ExpiresAt != nil { + expiresAt = *token.ExpiresAt + } + jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, JWTClaims{ jwt.StandardClaims{ Issuer: issuer, // times required to be in sec not millisec IssuedAt: token.IssuedAt / 1000, - ExpiresAt: token.ExpiresAt / 1000, + ExpiresAt: expiresAt / 1000, }, token.Type, token.ID, diff --git a/internal/token/token.go b/internal/token/token.go index 401f41a55..b415adf28 100644 --- a/internal/token/token.go +++ b/internal/token/token.go @@ -12,6 +12,8 @@ import ( "github.com/harness/gitness/internal/store" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" + + "github.com/gotidy/ptr" ) const ( @@ -28,14 +30,14 @@ func CreateUserSession(ctx context.Context, tokenStore store.TokenStore, principal, principal, uid, - userTokenLifeTime, + ptr.Duration(userTokenLifeTime), enum.AccessGrantAll, ) } func CreatePAT(ctx context.Context, tokenStore store.TokenStore, createdBy *types.Principal, createdFor *types.User, - uid string, lifetime time.Duration, grants enum.AccessGrant) (*types.Token, string, error) { + uid string, lifetime *time.Duration, grants enum.AccessGrant) (*types.Token, string, error) { return Create( ctx, tokenStore, @@ -50,7 +52,7 @@ func CreatePAT(ctx context.Context, tokenStore store.TokenStore, func CreateSAT(ctx context.Context, tokenStore store.TokenStore, createdBy *types.Principal, createdFor *types.ServiceAccount, - uid string, lifetime time.Duration, grants enum.AccessGrant) (*types.Token, string, error) { + uid string, lifetime *time.Duration, grants enum.AccessGrant) (*types.Token, string, error) { return Create( ctx, tokenStore, @@ -65,9 +67,13 @@ func CreateSAT(ctx context.Context, tokenStore store.TokenStore, func Create(ctx context.Context, tokenStore store.TokenStore, tokenType enum.TokenType, createdBy *types.Principal, createdFor *types.Principal, - uid string, lifetime time.Duration, grants enum.AccessGrant) (*types.Token, string, error) { + uid string, lifetime *time.Duration, grants enum.AccessGrant) (*types.Token, string, error) { issuedAt := time.Now() - expiresAt := issuedAt.Add(lifetime) + + var expiresAt *int64 + if lifetime != nil { + expiresAt = ptr.Int64(issuedAt.Add(*lifetime).UnixMilli()) + } // create db entry first so we get the id. token := types.Token{ @@ -75,7 +81,7 @@ func Create(ctx context.Context, tokenStore store.TokenStore, UID: uid, PrincipalID: createdFor.ID, IssuedAt: issuedAt.UnixMilli(), - ExpiresAt: expiresAt.UnixMilli(), + ExpiresAt: expiresAt, Grants: grants, CreatedBy: createdBy.ID, } diff --git a/lock/memory.go b/lock/memory.go index 25162a4b6..dd7870e9e 100644 --- a/lock/memory.go +++ b/lock/memory.go @@ -69,7 +69,7 @@ func (m *InMemory) NewMutex(key string, options ...Option) (Mutex, error) { // https://github.com/go-redsync/redsync/blob/e1e5da6654c81a2069d6a360f1a31c21f05cd22d/mutex.go#LL81C4-L81C100 waitTime := config.Expiry if config.TimeoutFactor > 0 { - waitTime *= time.Duration(int64(float64(config.Expiry) * config.TimeoutFactor)) + waitTime = time.Duration(int64(float64(config.Expiry) * config.TimeoutFactor)) } lock := inMemMutex{ diff --git a/mocks/mock_client.go b/mocks/mock_client.go index 3216e04cb..b16a6490e 100644 --- a/mocks/mock_client.go +++ b/mocks/mock_client.go @@ -37,33 +37,33 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder { } // Login mocks base method. -func (m *MockClient) Login(arg0 context.Context, arg1, arg2 string) (*types.TokenResponse, error) { +func (m *MockClient) Login(arg0 context.Context, arg1 *user.LoginInput) (*types.TokenResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Login", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "Login", arg0, arg1) ret0, _ := ret[0].(*types.TokenResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // Login indicates an expected call of Login. -func (mr *MockClientMockRecorder) Login(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) Login(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockClient)(nil).Login), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockClient)(nil).Login), arg0, arg1) } // Register mocks base method. -func (m *MockClient) Register(arg0 context.Context, arg1, arg2, arg3, arg4 string) (*types.TokenResponse, error) { +func (m *MockClient) Register(arg0 context.Context, arg1 *user.RegisterInput) (*types.TokenResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Register", arg0, arg1, arg2, arg3, arg4) + ret := m.ctrl.Call(m, "Register", arg0, arg1) ret0, _ := ret[0].(*types.TokenResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // Register indicates an expected call of Register. -func (mr *MockClientMockRecorder) Register(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) Register(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockClient)(nil).Register), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockClient)(nil).Register), arg0, arg1) } // Self mocks base method. diff --git a/mocks/mock_store.go b/mocks/mock_store.go index 615be74a8..0af0bbc34 100644 --- a/mocks/mock_store.go +++ b/mocks/mock_store.go @@ -67,18 +67,18 @@ func (mr *MockPrincipalStoreMockRecorder) CountServices(arg0 interface{}) *gomoc } // CountUsers mocks base method. -func (m *MockPrincipalStore) CountUsers(arg0 context.Context) (int64, error) { +func (m *MockPrincipalStore) CountUsers(arg0 context.Context, arg1 *types.UserFilter) (int64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CountUsers", arg0) + ret := m.ctrl.Call(m, "CountUsers", arg0, arg1) ret0, _ := ret[0].(int64) ret1, _ := ret[1].(error) return ret0, ret1 } // CountUsers indicates an expected call of CountUsers. -func (mr *MockPrincipalStoreMockRecorder) CountUsers(arg0 interface{}) *gomock.Call { +func (mr *MockPrincipalStoreMockRecorder) CountUsers(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountUsers", reflect.TypeOf((*MockPrincipalStore)(nil).CountUsers), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountUsers", reflect.TypeOf((*MockPrincipalStore)(nil).CountUsers), arg0, arg1) } // CreateService mocks base method. @@ -529,10 +529,10 @@ func (mr *MockSpaceStoreMockRecorder) FindByRef(arg0, arg1 interface{}) *gomock. } // List mocks base method. -func (m *MockSpaceStore) List(arg0 context.Context, arg1 int64, arg2 *types.SpaceFilter) ([]*types.Space, error) { +func (m *MockSpaceStore) List(arg0 context.Context, arg1 int64, arg2 *types.SpaceFilter) ([]types.Space, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "List", arg0, arg1, arg2) - ret0, _ := ret[0].([]*types.Space) + ret0, _ := ret[0].([]types.Space) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/store/database/util_sqlite.go b/store/database/util_sqlite.go index 9eb47aef9..7c044fa57 100644 --- a/store/database/util_sqlite.go +++ b/store/database/util_sqlite.go @@ -15,7 +15,8 @@ import ( func isSQLUniqueConstraintError(original error) bool { var sqliteErr sqlite3.Error if errors.As(original, &sqliteErr) { - return errors.Is(sqliteErr.ExtendedCode, sqlite3.ErrConstraintUnique) + return errors.Is(sqliteErr.ExtendedCode, sqlite3.ErrConstraintUnique) || + errors.Is(sqliteErr.ExtendedCode, sqlite3.ErrConstraintPrimaryKey) } return false diff --git a/types/check/token.go b/types/check/token.go index 6621e4a97..a370f8be7 100644 --- a/types/check/token.go +++ b/types/check/token.go @@ -17,11 +17,22 @@ var ( ErrTokenLifeTimeOutOfBounds = &ValidationError{ "The life time of a token has to be between 1 day and 365 days.", } + ErrTokenLifeTimeRequired = &ValidationError{ + "The life time of a token is required.", + } ) // TokenLifetime returns true if the lifetime is valid for a token. -func TokenLifetime(lifetime time.Duration) error { - if lifetime < minTokenLifeTime || lifetime > maxTokenLifeTime { +func TokenLifetime(lifetime *time.Duration, optional bool) error { + if lifetime == nil && !optional { + return ErrTokenLifeTimeRequired + } + + if lifetime == nil { + return nil + } + + if *lifetime < minTokenLifeTime || *lifetime > maxTokenLifeTime { return ErrTokenLifeTimeOutOfBounds } diff --git a/types/config.go b/types/config.go index 014b0cf55..b0ad4d2ba 100644 --- a/types/config.go +++ b/types/config.go @@ -14,7 +14,10 @@ type Config struct { // NOTE: If the value is not provided the hostname of the machine is used. InstanceID string `envconfig:"GITNESS_INSTANCE_ID"` Debug bool `envconfig:"GITNESS_DEBUG"` - Trace bool `envconfig:"GITNESS_TRACE"` + Trace bool `envconfig:"GITNESS_TRACE"`// GracefulShutdownTime defines the max time we wait when shutting down a server. + // 5min should be enough for most git clones to complete. + GracefulShutdownTime time.Duration `envconfig:"GITNESS_GRACEFUL_SHUTDOWN_TIME" default:"300s"` + AllowSignUp bool `envconfig:"GITNESS_ALLOW_SIGNUP"` Profiler struct { diff --git a/types/enum/membership.go b/types/enum/membership.go new file mode 100644 index 000000000..42a63d73b --- /dev/null +++ b/types/enum/membership.go @@ -0,0 +1,46 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package enum + +import ( + "strings" +) + +// MembershipSort represents membership sort order. +type MembershipSort int + +// Order enumeration. +const ( + MembershipSortNone MembershipSort = iota + MembershipSortName + MembershipSortCreated +) + +// ParseMembershipSort parses the membership sort attribute string +// and returns the equivalent enumeration. +func ParseMembershipSort(s string) MembershipSort { + switch strings.ToLower(s) { + case name: + return MembershipSortName + case created, createdAt: + return MembershipSortCreated + default: + return MembershipSortNone + } +} + +// String returns the string representation of the attribute. +func (a MembershipSort) String() string { + switch a { + case MembershipSortName: + return name + case MembershipSortCreated: + return created + case MembershipSortNone: + return "" + default: + return undefined + } +} diff --git a/types/enum/membership_role.go b/types/enum/membership_role.go new file mode 100644 index 000000000..f7888d2c2 --- /dev/null +++ b/types/enum/membership_role.go @@ -0,0 +1,80 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package enum + +import "golang.org/x/exp/slices" + +// MembershipRole represents the different level of space memberships (permission set). +type MembershipRole string + +func (MembershipRole) Enum() []interface{} { return toInterfaceSlice(MembershipRoles) } +func (m MembershipRole) Sanitize() (MembershipRole, bool) { return Sanitize(m, GetAllMembershipRoles) } +func GetAllMembershipRoles() ([]MembershipRole, MembershipRole) { return MembershipRoles, "" } + +var MembershipRoles = sortEnum([]MembershipRole{ + MembershipRoleReader, + MembershipRoleExecutor, + MembershipRoleContributor, + MembershipRoleSpaceOwner, +}) + +var membershipRoleReaderPermissions = slices.Clip(slices.Insert([]Permission{}, 0, + PermissionRepoView, + PermissionSpaceView, + PermissionServiceAccountView, +)) + +var membershipRoleExecutorPermissions = slices.Clip(slices.Insert(membershipRoleReaderPermissions, 0, + PermissionCommitCheckReport, +)) + +var membershipRoleContributorPermissions = slices.Clip(slices.Insert(membershipRoleReaderPermissions, 0, + PermissionRepoPush, +)) + +var membershipRoleSpaceOwnerPermissions = slices.Clip(slices.Insert(membershipRoleReaderPermissions, 0, + PermissionRepoEdit, + PermissionRepoDelete, + PermissionRepoPush, + PermissionCommitCheckReport, + + PermissionSpaceEdit, + PermissionSpaceCreate, + PermissionSpaceDelete, + + PermissionServiceAccountCreate, + PermissionServiceAccountEdit, + PermissionServiceAccountDelete, +)) + +func init() { + slices.Sort(membershipRoleReaderPermissions) + slices.Sort(membershipRoleExecutorPermissions) + slices.Sort(membershipRoleContributorPermissions) + slices.Sort(membershipRoleSpaceOwnerPermissions) +} + +// Permissions returns the list of permissions for the role. +func (m MembershipRole) Permissions() []Permission { + switch m { + case MembershipRoleReader: + return membershipRoleReaderPermissions + case MembershipRoleExecutor: + return membershipRoleExecutorPermissions + case MembershipRoleContributor: + return membershipRoleContributorPermissions + case MembershipRoleSpaceOwner: + return membershipRoleSpaceOwnerPermissions + default: + return nil + } +} + +const ( + MembershipRoleReader MembershipRole = "reader" + MembershipRoleExecutor MembershipRole = "executor" + MembershipRoleContributor MembershipRole = "contributor" + MembershipRoleSpaceOwner MembershipRole = "space_owner" +) diff --git a/types/enum/permission.go b/types/enum/permission.go index 1a3302156..6824f035a 100644 --- a/types/enum/permission.go +++ b/types/enum/permission.go @@ -47,7 +47,7 @@ const ( PermissionUserView Permission = "user_view" PermissionUserEdit Permission = "user_edit" PermissionUserDelete Permission = "user_delete" - PermissionUserEditAdmin Permission = "user_editadmin" + PermissionUserEditAdmin Permission = "user_editAdmin" ) const ( @@ -68,7 +68,7 @@ const ( PermissionServiceView Permission = "service_view" PermissionServiceEdit Permission = "service_edit" PermissionServiceDelete Permission = "service_delete" - PermissionServiceEditAdmin Permission = "service_editadmin" + PermissionServiceEditAdmin Permission = "service_editAdmin" ) const ( diff --git a/types/enum/user_test.go b/types/enum/user_test.go index cc0845499..010ccd5c3 100644 --- a/types/enum/user_test.go +++ b/types/enum/user_test.go @@ -11,7 +11,7 @@ func TestParseUserAttr(t *testing.T) { text string want UserAttr }{ - {"id", UserAttrUID}, + {"uid", UserAttrUID}, {"name", UserAttrName}, {"email", UserAttrEmail}, {"created", UserAttrCreated}, diff --git a/types/membership.go b/types/membership.go new file mode 100644 index 000000000..e8a908c23 --- /dev/null +++ b/types/membership.go @@ -0,0 +1,49 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package types + +import ( + "github.com/harness/gitness/types/enum" +) + +// MembershipKey can be used as a key for finding a user's space membership info. +type MembershipKey struct { + SpaceID int64 + PrincipalID int64 +} + +// Membership represents a user's membership of a space. +type Membership struct { + MembershipKey `json:"-"` + + CreatedBy int64 `json:"-"` + Created int64 `json:"created"` + Updated int64 `json:"updated"` + + Role enum.MembershipRole `json:"role"` +} + +// MembershipUser adds user info to the Membership data. +type MembershipUser struct { + Membership + Principal PrincipalInfo `json:"principal"` + AddedBy PrincipalInfo `json:"added_by"` +} + +// MembershipSpace adds space info to the Membership data. +type MembershipSpace struct { + Membership + Space Space `json:"space"` + AddedBy PrincipalInfo `json:"added_by"` +} + +// MembershipFilter holds membership query parameters. +type MembershipFilter struct { + Page int `json:"page"` + Size int `json:"size"` + Query string `json:"query"` + Sort enum.MembershipSort `json:"sort"` + Order enum.Order `json:"order"` +} diff --git a/types/token.go b/types/token.go index c73447cd6..80b65dfac 100644 --- a/types/token.go +++ b/types/token.go @@ -11,14 +11,16 @@ import ( // Represents server side infos stored for tokens we distribute. type Token struct { // TODO: int64 ID doesn't match DB - ID int64 `db:"token_id" json:"-"` - PrincipalID int64 `db:"token_principal_id" json:"principal_id"` - Type enum.TokenType `db:"token_type" json:"type"` - UID string `db:"token_uid" json:"uid"` - ExpiresAt int64 `db:"token_expires_at" json:"expires_at"` - IssuedAt int64 `db:"token_issued_at" json:"issued_at"` - Grants enum.AccessGrant `db:"token_grants" json:"grants"` - CreatedBy int64 `db:"token_created_by" json:"created_by"` + ID int64 `db:"token_id" json:"-"` + PrincipalID int64 `db:"token_principal_id" json:"principal_id"` + Type enum.TokenType `db:"token_type" json:"type"` + UID string `db:"token_uid" json:"uid"` + // ExpiresAt is an optional unix time that if specified restricts the validity of a token. + ExpiresAt *int64 `db:"token_expires_at" json:"expires_at,omitempty"` + // IssuedAt is the unix time at which the token was issued. + IssuedAt int64 `db:"token_issued_at" json:"issued_at"` + Grants enum.AccessGrant `db:"token_grants" json:"grants"` + CreatedBy int64 `db:"token_created_by" json:"created_by"` } // TokenResponse is returned as part of token creation for PAT / SAT / User Session. diff --git a/types/user.go b/types/user.go index a3881fa72..2afe5aa22 100644 --- a/types/user.go +++ b/types/user.go @@ -42,6 +42,7 @@ type ( Size int `json:"size"` Sort enum.UserAttr `json:"sort"` Order enum.Order `json:"order"` + Admin bool `json:"admin"` } ) diff --git a/web/config/moduleFederation.config.js b/web/config/moduleFederation.config.js index 384ba5d64..8b4f0ccb3 100644 --- a/web/config/moduleFederation.config.js +++ b/web/config/moduleFederation.config.js @@ -37,7 +37,8 @@ module.exports = { './Settings': './src/pages/RepositorySettings/RepositorySettings.tsx', './Webhooks': './src/pages/Webhooks/Webhooks.tsx', './WebhookNew': './src/pages/WebhookNew/WebhookNew.tsx', - './WebhookDetails': './src/pages/WebhookDetails/WebhookDetails.tsx' + './WebhookDetails': './src/pages/WebhookDetails/WebhookDetails.tsx', + './NewRepoModalButton': './src/components/NewRepoModalButton/NewRepoModalButton.tsx' }, shared: { formik: packageJSON.dependencies['formik'], diff --git a/web/config/webpack.common.js b/web/config/webpack.common.js index c71e03d8a..c96556268 100644 --- a/web/config/webpack.common.js +++ b/web/config/webpack.common.js @@ -12,9 +12,7 @@ const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin') const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin') const moduleFederationConfig = require('./moduleFederation.config') const CONTEXT = process.cwd() - const DEV = process.env.NODE_ENV === 'development' -const ON_PREM = `${process.env.ON_PREM}` === 'true' module.exports = { target: 'web', @@ -159,8 +157,7 @@ module.exports = { new ModuleFederationPlugin(moduleFederationConfig), new DefinePlugin({ 'process.env': '{}', // required for @blueprintjs/core - __DEV__: DEV, - __ON_PREM__: ON_PREM + __DEV__: DEV }), new GenerateStringTypesPlugin(), new RetryChunkLoadPlugin({ diff --git a/web/config/webpack.prod.js b/web/config/webpack.prod.js index 8d3a4816f..162fc1da8 100644 --- a/web/config/webpack.prod.js +++ b/web/config/webpack.prod.js @@ -1,15 +1,17 @@ const { merge } = require('webpack-merge') +const path = require('path') const HTMLWebpackPlugin = require('html-webpack-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const { DefinePlugin } = require('webpack') const commonConfig = require('./webpack.common') - -const ON_PREM = `${process.env.ON_PREM}` === 'true' +const CONTEXT = process.cwd() const prodConfig = { + context: CONTEXT, + entry: path.resolve(CONTEXT, '/src/index.tsx'), mode: 'production', - devtool: 'source-map', + devtool: process.env.ENABLE_SOURCE_MAP ? 'source-map' : false, output: { filename: '[name].[contenthash:6].js', chunkFilename: '[name].[id].[contenthash:6].js' @@ -39,9 +41,7 @@ const prodConfig = { filename: 'index.html', favicon: 'src/favicon.svg', minify: false, - templateParameters: { - __ON_PREM__: ON_PREM - } + templateParameters: {} }) ] } diff --git a/web/dist.go b/web/dist.go index 0b54c4836..58adb79c5 100644 --- a/web/dist.go +++ b/web/dist.go @@ -1,7 +1,3 @@ -// Copyright 2022 Harness Inc. All rights reserved. -// Use of this source code is governed by the Polyform Free Trial License -// that can be found in the LICENSE.md file for this repository. - //go:build !proxy // +build !proxy diff --git a/web/src/App.module.scss b/web/src/App.module.scss index 08c0d2950..1e9464c87 100644 --- a/web/src/App.module.scss +++ b/web/src/App.module.scss @@ -1,3 +1,5 @@ +@import 'src/utils/vars'; + /* * NOTE: Styles in this file are loaded in both standalone and embedded * versions. Be careful! Don't introduce global states that could be conflict @@ -5,10 +7,7 @@ */ .main { - --nav-item-width: var(--main-nav-width, 88px); - --nav-item-height: 80px; - --nav-menu-width: 184px; - --nav-width: calc(var(--nav-item-width) + var(--nav-menu-width)); + @include vars; &.fullPage { height: 100%; @@ -59,5 +58,9 @@ .Resizer.disabled:hover { border-color: transparent; } + + div[data-testid='page-body'] > div[data-testid='page-error'] { + height: 80vh !important; + } } } diff --git a/web/src/AppContext.tsx b/web/src/AppContext.tsx index 5236d4c03..e07a1250d 100644 --- a/web/src/AppContext.tsx +++ b/web/src/AppContext.tsx @@ -4,6 +4,7 @@ import { useGet } from 'restful-react' import type { AppProps } from 'AppProps' import { routes } from 'RouteDefinitions' import type { TypesUser } from 'services/code' +import { useAPIToken } from 'hooks/useAPIToken' interface AppContextProps extends AppProps { setAppContext: (value: Partial) => void @@ -33,7 +34,11 @@ export const AppContextProvider: React.FC<{ value: AppProps }> = React.memo(func value: initialValue, children }) { - const { data: currentUser = defaultCurrentUser } = useGet({ path: '/api/v1/user' }) + const [token, setToken] = useAPIToken() + const { data: currentUser = defaultCurrentUser, error } = useGet({ + path: '/api/v1/user', + lazy: initialValue.standalone && !token + }) const [appStates, setAppStates] = useState(initialValue) useEffect(() => { @@ -42,6 +47,12 @@ export const AppContextProvider: React.FC<{ value: AppProps }> = React.memo(func } }, [initialValue, appStates]) + useEffect(() => { + if (initialValue.standalone && error) { + setToken('') + } + }, [initialValue.standalone, error, setToken]) + return ( , 'repoPath' | 'branch' | 'tags'>> = { - space: ':space', + space: ':space*', repoName: ':repoName', gitRef: ':gitRef*', resourcePath: ':resourcePath*', @@ -34,7 +34,10 @@ export interface CODERoutes { toRegister: () => string toCODEHome: () => string - toCODESpaces: () => string + + toCODESpaceAccessControl: (args: Required>) => string + toCODESpaceSettings: (args: Required>) => string + toCODEGlobalSettings: () => string toCODEUsers: () => string toCODEUserProfile: () => string @@ -66,7 +69,10 @@ export const routes: CODERoutes = { toRegister: (): string => '/register', toCODEHome: () => `/`, - toCODESpaces: () => `/spaces`, + + toCODESpaceAccessControl: ({ space }) => `/access-control/${space}`, + toCODESpaceSettings: ({ space }) => `/settings/${space}`, + toCODEGlobalSettings: () => '/settings', toCODEUsers: () => '/users', toCODEUserProfile: () => '/profile', diff --git a/web/src/RouteDestinations.tsx b/web/src/RouteDestinations.tsx index 041f0642d..667d5a8d4 100644 --- a/web/src/RouteDestinations.tsx +++ b/web/src/RouteDestinations.tsx @@ -5,14 +5,10 @@ import { SignUp } from 'pages/SignUp/SignUp' import Repository from 'pages/Repository/Repository' import { routes, pathProps } from 'RouteDefinitions' import RepositoriesListing from 'pages/RepositoriesListing/RepositoriesListing' -import Spaces from 'pages/Spaces/Spaces' - -import { LayoutWithSideMenu, LayoutWithSideNav } from 'layouts/layout' -import { GlobalSettingsMenu } from 'layouts/menu/GlobalSettingsMenu' -import { RepositoryMenu } from 'layouts/menu/RepositoryMenu' -import { AdminMenu } from 'layouts/menu/AdminMenu' +import { LayoutWithSideNav, LayoutWithoutSideNav } from 'layouts/layout' import RepositoryFileEdit from 'pages/RepositoryFileEdit/RepositoryFileEdit' import RepositoryCommits from 'pages/RepositoryCommits/RepositoryCommits' +import RepositoryCommit from 'pages/RepositoryCommit/RepositoryCommit' import RepositoryBranches from 'pages/RepositoryBranches/RepositoryBranches' import RepositoryTags from 'pages/RepositoryTags/RepositoryTags' import Compare from 'pages/Compare/Compare' @@ -26,24 +22,38 @@ import UsersListing from 'pages/UsersListing/UsersListing' import Home from 'pages/Home/Home' import UserProfile from 'pages/UserProfile/UserProfile' import ChangePassword from 'pages/ChangePassword/ChangePassword' +import SpaceAccessControl from 'pages/SpaceAccessControl/SpaceAccessControl' +import SpaceSettings from 'pages/SpaceSettings/SpaceSettings' +import { useStrings } from 'framework/strings' export const RouteDestinations: React.FC = React.memo(function RouteDestinations() { + const { getString } = useStrings() const repoPath = `${pathProps.space}/${pathProps.repoName}` return ( - + + + - + + + - - - + + + + + + + + + @@ -52,9 +62,33 @@ export const RouteDestinations: React.FC = React.memo(function RouteDestinations repoPath, diffRefs: pathProps.diffRefs })}> - }> + - + + + + + + + + + + + + + + + + + + + + + + + + + - }> + - + - }> + - + - }> + - + - }> + - + - }> + - + - }> + - + - }> + - + + + + + + + - }> + - + - }> + - + - }> + - - - - - }> - - - - - - }> - - - - - - }> - - - - - - }> - - - - - - - - - }> - - + + + + + - - }> - - + + + + + - - }> - - + + + + + diff --git a/web/src/components/BranchTagSelect/BranchTagSelect.module.scss b/web/src/components/BranchTagSelect/BranchTagSelect.module.scss index 23d84233d..4ebf838fa 100644 --- a/web/src/components/BranchTagSelect/BranchTagSelect.module.scss +++ b/web/src/components/BranchTagSelect/BranchTagSelect.module.scss @@ -1,3 +1,9 @@ +html[class=''] { + .button:focus { + --border: 1px solid var(--primary-7) !important; + } +} + .button { --border: 1px solid var(--grey-200) !important; --background-color-active: var(--white) !important; diff --git a/web/src/components/BranchTagSelect/BranchTagSelect.tsx b/web/src/components/BranchTagSelect/BranchTagSelect.tsx index ef0dd1d99..0894d8a3c 100644 --- a/web/src/components/BranchTagSelect/BranchTagSelect.tsx +++ b/web/src/components/BranchTagSelect/BranchTagSelect.tsx @@ -1,10 +1,3 @@ -/* - * Copyright 2021 Harness Inc. All rights reserved. - * Use of this source code is governed by the PolyForm Shield 1.0.0 license - * that can be found in the licenses directory at the root of this repository, also available at - * https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt. - */ - import React, { useEffect, useMemo, useRef, useState } from 'react' import { Classes, Icon as BPIcon, Menu, MenuItem, PopoverPosition } from '@blueprintjs/core' import { diff --git a/web/src/components/Changes/Changes.tsx b/web/src/components/Changes/Changes.tsx index 0e7c28879..f5bf3a9b8 100644 --- a/web/src/components/Changes/Changes.tsx +++ b/web/src/components/Changes/Changes.tsx @@ -76,8 +76,8 @@ export const Changes: React.FC = ({ refetch, response } = useGet({ - path: `/api/v1/repos/${repoMetadata?.path}/+/${ - pullRequestMetadata ? `pullreq/${pullRequestMetadata.number}/diff` : `compare/${targetBranch}...${sourceBranch}` + path: `/api/v1/repos/${repoMetadata?.path}/+/compare/${ + pullRequestMetadata ? `${pullRequestMetadata.merge_base_sha}...${pullRequestMetadata.source_sha}` : `${targetBranch}...${sourceBranch}` }`, lazy: !targetBranch || !sourceBranch }) diff --git a/web/src/components/CommitModalButton/CommitModalButton.tsx b/web/src/components/CommitModalButton/CommitModalButton.tsx index fdf39a313..9e0c73ba9 100644 --- a/web/src/components/CommitModalButton/CommitModalButton.tsx +++ b/web/src/components/CommitModalButton/CommitModalButton.tsx @@ -1,10 +1,3 @@ -/* - * Copyright 2021 Harness Inc. All rights reserved. - * Use of this source code is governed by the PolyForm Shield 1.0.0 license - * that can be found in the licenses directory at the root of this repository, also available at - * https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt. - */ - import React, { useState } from 'react' import { Dialog, Intent } from '@blueprintjs/core' import * as yup from 'yup' diff --git a/web/src/components/CommitsView/CommitsView.tsx b/web/src/components/CommitsView/CommitsView.tsx index dde8ba5a5..17a3053a1 100644 --- a/web/src/components/CommitsView/CommitsView.tsx +++ b/web/src/components/CommitsView/CommitsView.tsx @@ -187,7 +187,12 @@ export function CommitsView({ + {getString('commitsOn', { date })} }> diff --git a/web/src/components/CreateBranchModal/CreateBranchModal.tsx b/web/src/components/CreateBranchModal/CreateBranchModal.tsx index 05a4ee719..1a6bf593f 100644 --- a/web/src/components/CreateBranchModal/CreateBranchModal.tsx +++ b/web/src/components/CreateBranchModal/CreateBranchModal.tsx @@ -1,10 +1,3 @@ -/* - * Copyright 2021 Harness Inc. All rights reserved. - * Use of this source code is governed by the PolyForm Shield 1.0.0 license - * that can be found in the licenses directory at the root of this repository, also available at - * https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt. - */ - import React, { useCallback, useState } from 'react' import { Dialog, Intent } from '@blueprintjs/core' import * as yup from 'yup' diff --git a/web/src/components/CreateTagModal/CreateTagModal.tsx b/web/src/components/CreateTagModal/CreateTagModal.tsx index a37c8a39c..ed700df9a 100644 --- a/web/src/components/CreateTagModal/CreateTagModal.tsx +++ b/web/src/components/CreateTagModal/CreateTagModal.tsx @@ -1,10 +1,3 @@ -/* - * Copyright 2021 Harness Inc. All rights reserved. - * Use of this source code is governed by the PolyForm Shield 1.0.0 license - * that can be found in the licenses directory at the root of this repository, also available at - * https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt. - */ - import React, { useCallback, useState } from 'react' import { Dialog, Intent } from '@blueprintjs/core' import * as yup from 'yup' diff --git a/web/src/components/GitnessLogo/GitnessLogo.module.scss b/web/src/components/GitnessLogo/GitnessLogo.module.scss new file mode 100644 index 000000000..d99db0b26 --- /dev/null +++ b/web/src/components/GitnessLogo/GitnessLogo.module.scss @@ -0,0 +1,20 @@ +.main { + margin: var(--spacing-large) var(--spacing-large) 0 !important; + padding-bottom: 6px !important; + + > a { + display: block; + } + + .layout { + display: inline-flex !important; + align-items: center !important; + } + + .text { + font-size: 20px; + font-style: normal; + font-weight: 600; + color: var(--black); + } +} diff --git a/web/src/pages/Spaces/Spaces.module.scss.d.ts b/web/src/components/GitnessLogo/GitnessLogo.module.scss.d.ts similarity index 67% rename from web/src/pages/Spaces/Spaces.module.scss.d.ts rename to web/src/components/GitnessLogo/GitnessLogo.module.scss.d.ts index 393b0bb14..a02e77e93 100644 --- a/web/src/pages/Spaces/Spaces.module.scss.d.ts +++ b/web/src/components/GitnessLogo/GitnessLogo.module.scss.d.ts @@ -2,7 +2,7 @@ // this is an auto-generated file declare const styles: { readonly main: string - readonly pageHeading: string - readonly pageContent: string + readonly layout: string + readonly text: string } export default styles diff --git a/web/src/components/GitnessLogo/GitnessLogo.tsx b/web/src/components/GitnessLogo/GitnessLogo.tsx new file mode 100644 index 000000000..9c32b811f --- /dev/null +++ b/web/src/components/GitnessLogo/GitnessLogo.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import { Container, Icon, Layout, Text } from '@harness/uicore' +import { Link } from 'react-router-dom' +import { useStrings } from 'framework/strings' +import { routes } from 'RouteDefinitions' +import css from './GitnessLogo.module.scss' + +export const GitnessLogo: React.FC = () => { + const { getString } = useStrings() + + return ( + + + + + {getString('gitness')} + + + + ) +} diff --git a/web/src/components/NewRepoModalButton/NewRepoModalButton.tsx b/web/src/components/NewRepoModalButton/NewRepoModalButton.tsx index 71abbed1f..b9ae369a9 100644 --- a/web/src/components/NewRepoModalButton/NewRepoModalButton.tsx +++ b/web/src/components/NewRepoModalButton/NewRepoModalButton.tsx @@ -1,10 +1,3 @@ -/* - * Copyright 2021 Harness Inc. All rights reserved. - * Use of this source code is governed by the PolyForm Shield 1.0.0 license - * that can be found in the licenses directory at the root of this repository, also available at - * https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt. - */ - import React, { useEffect, useMemo, useState } from 'react' import { Icon as BPIcon, Classes, Dialog, Intent, Menu, MenuDivider, MenuItem } from '@blueprintjs/core' import * as yup from 'yup' @@ -84,7 +77,6 @@ export const NewRepoModalButton: React.FC = ({ ...props }) => { const ModalComponent: React.FC = () => { - const { standalone } = useAppContext() const { getString } = useStrings() const [branchName, setBranchName] = useState(DEFAULT_BRANCH_NAME) const { showError } = useToaster() @@ -92,9 +84,11 @@ export const NewRepoModalButton: React.FC = ({ const { mutate: createRepo, loading: submitLoading } = useMutate({ verb: 'POST', path: `/api/v1/repos`, - queryParams: { - space_path: space - } + queryParams: standalone + ? undefined + : { + space_path: space + } }) const { data: gitignores, @@ -124,7 +118,7 @@ export const NewRepoModalButton: React.FC = ({ license: get(formData, 'license', 'none'), uid: get(formData, 'name', '').trim(), readme: get(formData, 'addReadme', false), - parent_id: standalone ? Number(space) : 0 // TODO: Backend needs to fix parentID: accept string or number + parent_ref: space } createRepo(payload) .then(response => { @@ -359,3 +353,5 @@ const BranchName: React.FC = ({ currentBranchName, onSelect }) ) } + +export default NewRepoModalButton diff --git a/web/src/components/NewSpaceModalButton/NewSpaceModalButton.module.scss b/web/src/components/NewSpaceModalButton/NewSpaceModalButton.module.scss new file mode 100644 index 000000000..9f2b00866 --- /dev/null +++ b/web/src/components/NewSpaceModalButton/NewSpaceModalButton.module.scss @@ -0,0 +1,3 @@ +.divider { + margin: var(--spacing-medium) 0 var(--spacing-large) 0 !important; +} diff --git a/web/src/components/NewSpaceModalButton/NewSpaceModalButton.module.scss.d.ts b/web/src/components/NewSpaceModalButton/NewSpaceModalButton.module.scss.d.ts new file mode 100644 index 000000000..6c0f6ebb6 --- /dev/null +++ b/web/src/components/NewSpaceModalButton/NewSpaceModalButton.module.scss.d.ts @@ -0,0 +1,6 @@ +/* eslint-disable */ +// this is an auto-generated file +declare const styles: { + readonly divider: string +} +export default styles diff --git a/web/src/components/NewSpaceModalButton/NewSpaceModalButton.tsx b/web/src/components/NewSpaceModalButton/NewSpaceModalButton.tsx new file mode 100644 index 000000000..a2f6ede04 --- /dev/null +++ b/web/src/components/NewSpaceModalButton/NewSpaceModalButton.tsx @@ -0,0 +1,173 @@ +import React from 'react' +import { Dialog, Intent } from '@blueprintjs/core' +import * as yup from 'yup' +import { + Button, + ButtonProps, + Container, + Layout, + FlexExpander, + Icon, + Formik, + FormikForm, + Heading, + useToaster, + FormInput +} from '@harness/uicore' +import { FontVariation } from '@harness/design-system' +import { useMutate } from 'restful-react' +import { get } from 'lodash-es' +import { useModalHook } from '@harness/use-modal' +import { useStrings } from 'framework/strings' +import { getErrorMessage, REGEX_VALID_REPO_NAME } from 'utils/Utils' +import type { TypesSpace, OpenapiCreateSpaceRequest } from 'services/code' +import { useAppContext } from 'AppContext' + +enum RepoVisibility { + PUBLIC = 'public', + PRIVATE = 'private' +} + +interface RepoFormData { + name: string + description: string + license: string + defaultBranch: string + gitignore: string + addReadme: boolean + isPublic: RepoVisibility +} + +const formInitialValues: RepoFormData = { + name: '', + description: '', + license: '', + defaultBranch: 'main', + gitignore: '', + addReadme: false, + isPublic: RepoVisibility.PRIVATE +} + +export interface NewSpaceModalButtonProps extends Omit { + space: string + modalTitle: string + submitButtonTitle?: string + cancelButtonTitle?: string + onRefetch: () => void +} +export interface OpenapiCreateSpaceRequestExtended extends OpenapiCreateSpaceRequest { + parent_id?: number +} + +export const NewSpaceModalButton: React.FC = ({ + space, + modalTitle, + submitButtonTitle, + cancelButtonTitle, + onRefetch, + ...props +}) => { + const ModalComponent: React.FC = () => { + const { standalone } = useAppContext() + const { getString } = useStrings() + const { showError } = useToaster() + + const { mutate: createSpace, loading: submitLoading } = useMutate({ + verb: 'POST', + path: `/api/v1/spaces` + }) + + const loading = submitLoading + + const handleSubmit = (formData: RepoFormData) => { + try { + const payload: OpenapiCreateSpaceRequestExtended = { + description: get(formData, 'description', '').trim(), + is_public: get(formData, 'isPublic') === RepoVisibility.PUBLIC, + uid: get(formData, 'name', '').trim(), + parent_id: standalone ? Number(space) : 0 // TODO: Backend needs to fix parentID: accept string or number + } + createSpace(payload) + .then(() => { + hideModal() + onRefetch() + }) + .catch(_error => { + showError(getErrorMessage(_error), 0, getString('failedToCreateSpace')) + }) + } catch (exception) { + showError(getErrorMessage(exception), 0, getString('failedToCreateSpace')) + } + } + + return ( + + + + {modalTitle} + + + + + + + + + + + ) + } + + const [openModal, hideModal] = useModalHook(ModalComponent) + + return + + + + + + ) + }} + + + + + ) +} diff --git a/web/src/pages/Spaces/Spaces.module.scss b/web/src/pages/Spaces/Spaces.module.scss deleted file mode 100644 index 6a417a3a7..000000000 --- a/web/src/pages/Spaces/Spaces.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -.main { - display: flex; - flex-direction: column; - - .pageHeading { - height: 73px; - } - - .pageContent { - background-color: var(--primary-bg) !important; - flex-grow: 1; - } -} diff --git a/web/src/pages/Spaces/Spaces.tsx b/web/src/pages/Spaces/Spaces.tsx deleted file mode 100644 index 307d7091d..000000000 --- a/web/src/pages/Spaces/Spaces.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react' -import { Container, Heading } from '@harness/uicore' -import { Link } from 'react-router-dom' -import { routes } from 'RouteDefinitions' -import { useStrings } from 'framework/strings' -import css from './Spaces.module.scss' - -export default function Spaces() { - const { getString } = useStrings() - - return ( - - - {getString('spaces')} - - - Test Space - - - ) -} diff --git a/web/src/pages/UserProfile/NewToken/NewToken.tsx b/web/src/pages/UserProfile/NewToken/NewToken.tsx index db395107f..05be2b0c4 100644 --- a/web/src/pages/UserProfile/NewToken/NewToken.tsx +++ b/web/src/pages/UserProfile/NewToken/NewToken.tsx @@ -1,13 +1,16 @@ -import React, { useMemo } from 'react' +import React, { useMemo, useState } from 'react' import { Button, ButtonVariation, Color, + Container, Dialog, + FlexExpander, FontVariation, FormikForm, FormInput, Layout, + SelectOption, Text } from '@harness/uicore' import { useModalHook } from '@harness/use-modal' @@ -15,77 +18,136 @@ import { Formik } from 'formik' import { useMutate } from 'restful-react' import moment from 'moment' import * as Yup from 'yup' - -import { REGEX_VALID_REPO_NAME } from 'utils/Utils' +import { Else, Match, Render, Truthy } from 'react-jsx-match' +import { omit } from 'lodash-es' import { useStrings } from 'framework/strings' +import type { OpenapiCreateTokenRequest } from 'services/code' +import { REGEX_VALID_REPO_NAME } from 'utils/Utils' +import { CodeIcon } from 'utils/GitUtils' +import { CopyButton } from 'components/CopyButton/CopyButton' +import { FormInputWithCopyButton } from 'components/UserManagementFlows/AddUserModal' + +import css from 'components/CloneCredentialDialog/CloneCredentialDialog.module.scss' const useNewToken = ({ onClose }: { onClose: () => void }) => { const { getString } = useStrings() const { mutate } = useMutate({ path: '/api/v1/user/tokens', verb: 'POST' }) - const lifeTimeOptions = useMemo( + const [generatedToken, setGeneratedToken] = useState() + const isTokenGenerated = Boolean(generatedToken) + + const lifeTimeOptions: SelectOption[] = useMemo( () => [ { label: getString('nDays', { number: 7 }), value: 604800000000000 }, { label: getString('nDays', { number: 30 }), value: 2592000000000000 }, { label: getString('nDays', { number: 60 }), value: 5184000000000000 }, - { label: getString('nDays', { number: 90 }), value: 7776000000000000 } + { label: getString('nDays', { number: 90 }), value: 7776000000000000 }, + { label: getString('noExpiration'), value: Infinity } ], [getString] ) + const onModalClose = () => { + setGeneratedToken('') + hideModal() + onClose() + } + const [openModal, hideModal] = useModalHook(() => { return ( - - + initialValues={{ - uid: '', - lifeTime: 0 + uid: '' }} validationSchema={Yup.object().shape({ uid: Yup.string() .required(getString('validation.nameIsRequired')) .matches(REGEX_VALID_REPO_NAME, getString('validation.nameInvalid')), - lifeTime: Yup.number().required(getString('validation.expirationDateRequired')) + lifetime: Yup.number().required(getString('validation.expirationDateRequired')) })} onSubmit={async values => { - await mutate(values) - hideModal() - onClose() + let payload = { ...values } + + if (payload.lifetime === Infinity) { + payload = omit(payload, 'lifetime') + } + + const res = await mutate(payload) + setGeneratedToken(res?.access_token) }}> {formikProps => { - const expiresAtString = moment(Date.now() + formikProps.values.lifeTime / 1000000).format( - 'dddd, MMMM DD YYYY' - ) + const lifetime = formikProps.values.lifetime || 0 + const expiresAtString = moment(Date.now() + lifetime / 1000000).format('dddd, MMMM DD YYYY') return ( - - - {formikProps.values.lifeTime ? ( - - {getString('newToken.expireOn', { date: expiresAtString })} + + {lifetime ? ( + + {lifetime === Infinity + ? getString('noExpirationDate') + : getString('newToken.expireOn', { date: expiresAtString })} ) : null} - - ) - }, []) + }, [generatedToken]) return { openModal, diff --git a/web/src/pages/UserProfile/UserProfile.tsx b/web/src/pages/UserProfile/UserProfile.tsx index 8f3b389a7..849149120 100644 --- a/web/src/pages/UserProfile/UserProfile.tsx +++ b/web/src/pages/UserProfile/UserProfile.tsx @@ -6,7 +6,6 @@ import { ButtonVariation, Card, Container, - FlexExpander, Layout, Page, TableV2, @@ -48,7 +47,7 @@ const UserProfile = () => { const { data: userTokens, loading: tokensLoading, refetch: refetchTokens } = useGet({ path: USER_TOKENS_API_PATH }) const { mutate: deleteToken } = useMutate({ path: USER_TOKENS_API_PATH, verb: 'DELETE' }) - const [_, setToken] = useAPIToken() + const [, setToken] = useAPIToken() const onLogout = async () => { await logoutUser() @@ -92,7 +91,7 @@ const UserProfile = () => { Header: getString('status'), width: '20%', Cell: ({ row }: CellProps) => { - const isActive = +Date.now() < Number(row.original.expires_at) + const isActive = !row.original.expires_at || +Date.now() < Number(row.original.expires_at) return ( { Cell: ({ row }: CellProps) => { return ( - {moment(row.original.expires_at).format('MMM Do, YYYY h:mm:ss a')} + {row.original.expires_at + ? moment(row.original.expires_at).format('MMM Do, YYYY h:mm:ss a') + : getString('noExpiration')} ) } @@ -164,7 +165,14 @@ const UserProfile = () => { - + diff --git a/web/src/pages/UsersListing/UsersListing.tsx b/web/src/pages/UsersListing/UsersListing.tsx index 28b16d9d6..a6b65661e 100644 --- a/web/src/pages/UsersListing/UsersListing.tsx +++ b/web/src/pages/UsersListing/UsersListing.tsx @@ -77,7 +77,13 @@ const UsersListing = () => { Cell: ({ row }: CellProps) => { return ( - + {row.original.display_name} diff --git a/web/src/pages/Webhooks/WebhooksHeader/WebhooksHeader.tsx b/web/src/pages/Webhooks/WebhooksHeader/WebhooksHeader.tsx index 987ec6407..09343d554 100644 --- a/web/src/pages/Webhooks/WebhooksHeader/WebhooksHeader.tsx +++ b/web/src/pages/Webhooks/WebhooksHeader/WebhooksHeader.tsx @@ -21,14 +21,8 @@ export function WebhooksHeader({ repoMetadata, loading, onSearchTermChanged }: W return ( -