Merge remote-tracking branch 'origin' into abhinav/CODE-830

This commit is contained in:
Abhinav Singh 2023-09-11 15:59:23 -07:00
commit 742eea6321
189 changed files with 2586 additions and 1666 deletions

View File

@ -13,6 +13,7 @@ import (
"github.com/harness/gitness/events" "github.com/harness/gitness/events"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
"github.com/harness/gitness/gitrpc/server" "github.com/harness/gitness/gitrpc/server"
"github.com/harness/gitness/internal/services/trigger"
"github.com/harness/gitness/internal/services/webhook" "github.com/harness/gitness/internal/services/webhook"
"github.com/harness/gitness/lock" "github.com/harness/gitness/lock"
"github.com/harness/gitness/store/database" "github.com/harness/gitness/store/database"
@ -147,23 +148,26 @@ func ProvideEventsConfig() (events.Config, error) {
return config, nil return config, nil
} }
// ProvideWebhookConfig loads the webhook config from the environment. // ProvideWebhookConfig loads the webhook service config from the main config.
// It backfills certain config elements if required. func ProvideWebhookConfig(config *types.Config) webhook.Config {
func ProvideWebhookConfig() (webhook.Config, error) { return webhook.Config{
config := webhook.Config{} UserAgentIdentity: config.Webhook.UserAgentIdentity,
err := envconfig.Process("", &config) HeaderIdentity: config.Webhook.HeaderIdentity,
if err != nil { EventReaderName: config.InstanceID,
return webhook.Config{}, fmt.Errorf("failed to load events config: %w", err) Concurrency: config.Webhook.Concurrency,
MaxRetries: config.Webhook.MaxRetries,
AllowPrivateNetwork: config.Webhook.AllowPrivateNetwork,
AllowLoopback: config.Webhook.AllowLoopback,
} }
}
if config.EventReaderName == "" { // ProvideTriggerConfig loads the trigger service config from the main config.
config.EventReaderName, err = getSanitizedMachineName() func ProvideTriggerConfig(config *types.Config) trigger.Config {
if err != nil { return trigger.Config{
return webhook.Config{}, fmt.Errorf("failed to get sanitized machine name: %w", err) EventReaderName: config.InstanceID,
} Concurrency: config.Webhook.Concurrency,
MaxRetries: config.Webhook.MaxRetries,
} }
return config, nil
} }
// ProvideLockConfig generates the `lock` package config from the gitness config. // ProvideLockConfig generates the `lock` package config from the gitness config.

View File

@ -33,7 +33,7 @@ import (
"github.com/harness/gitness/internal/api/controller/space" "github.com/harness/gitness/internal/api/controller/space"
"github.com/harness/gitness/internal/api/controller/system" "github.com/harness/gitness/internal/api/controller/system"
"github.com/harness/gitness/internal/api/controller/template" "github.com/harness/gitness/internal/api/controller/template"
"github.com/harness/gitness/internal/api/controller/trigger" controllertrigger "github.com/harness/gitness/internal/api/controller/trigger"
"github.com/harness/gitness/internal/api/controller/user" "github.com/harness/gitness/internal/api/controller/user"
controllerwebhook "github.com/harness/gitness/internal/api/controller/webhook" controllerwebhook "github.com/harness/gitness/internal/api/controller/webhook"
"github.com/harness/gitness/internal/auth/authn" "github.com/harness/gitness/internal/auth/authn"
@ -55,6 +55,7 @@ import (
"github.com/harness/gitness/internal/services/importer" "github.com/harness/gitness/internal/services/importer"
"github.com/harness/gitness/internal/services/job" "github.com/harness/gitness/internal/services/job"
pullreqservice "github.com/harness/gitness/internal/services/pullreq" pullreqservice "github.com/harness/gitness/internal/services/pullreq"
"github.com/harness/gitness/internal/services/trigger"
"github.com/harness/gitness/internal/services/webhook" "github.com/harness/gitness/internal/services/webhook"
"github.com/harness/gitness/internal/store" "github.com/harness/gitness/internal/store"
"github.com/harness/gitness/internal/store/cache" "github.com/harness/gitness/internal/store/cache"
@ -107,6 +108,8 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e
events.WireSet, events.WireSet,
cliserver.ProvideWebhookConfig, cliserver.ProvideWebhookConfig,
webhook.WireSet, webhook.WireSet,
cliserver.ProvideTriggerConfig,
trigger.WireSet,
githook.WireSet, githook.WireSet,
cliserver.ProvideLockConfig, cliserver.ProvideLockConfig,
lock.WireSet, lock.WireSet,
@ -130,7 +133,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e
eventsstream.WireSet, eventsstream.WireSet,
scheduler.WireSet, scheduler.WireSet,
commit.WireSet, commit.WireSet,
trigger.WireSet, controllertrigger.WireSet,
plugin.WireSet, plugin.WireSet,
importer.WireSet, importer.WireSet,
exporter.WireSet, exporter.WireSet,

View File

@ -52,6 +52,7 @@ import (
"github.com/harness/gitness/internal/services/importer" "github.com/harness/gitness/internal/services/importer"
"github.com/harness/gitness/internal/services/job" "github.com/harness/gitness/internal/services/job"
pullreq2 "github.com/harness/gitness/internal/services/pullreq" pullreq2 "github.com/harness/gitness/internal/services/pullreq"
trigger2 "github.com/harness/gitness/internal/services/trigger"
"github.com/harness/gitness/internal/services/webhook" "github.com/harness/gitness/internal/services/webhook"
"github.com/harness/gitness/internal/store" "github.com/harness/gitness/internal/store"
"github.com/harness/gitness/internal/store/cache" "github.com/harness/gitness/internal/store/cache"
@ -119,7 +120,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
repository, err := importer.ProvideRepoImporter(provider, gitrpcInterface, repoStore, jobScheduler, executor) repository, err := importer.ProvideRepoImporter(config, provider, gitrpcInterface, repoStore, jobScheduler, executor)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -142,7 +143,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
secretStore := database.ProvideSecretStore(db) secretStore := database.ProvideSecretStore(db)
connectorStore := database.ProvideConnectorStore(db) connectorStore := database.ProvideConnectorStore(db)
templateStore := database.ProvideTemplateStore(db) templateStore := database.ProvideTemplateStore(db)
spaceController := space.ProvideController(db, provider, eventsStreamer, pathUID, authorizer, pathStore, pipelineStore, secretStore, connectorStore, templateStore, spaceStore, repoStore, principalStore, repoController, membershipStore) spaceController := space.ProvideController(db, provider, eventsStreamer, pathUID, authorizer, pathStore, pipelineStore, secretStore, connectorStore, templateStore, spaceStore, repoStore, principalStore, repoController, membershipStore, repository)
pipelineController := pipeline.ProvideController(db, pathUID, pathStore, repoStore, authorizer, pipelineStore) pipelineController := pipeline.ProvideController(db, pathUID, pathStore, repoStore, authorizer, pipelineStore)
encrypter, err := encrypt.ProvideEncrypter(config) encrypter, err := encrypt.ProvideEncrypter(config)
if err != nil { if err != nil {
@ -174,10 +175,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
} }
migrator := codecomments.ProvideMigrator(gitrpcInterface) migrator := codecomments.ProvideMigrator(gitrpcInterface)
pullreqController := pullreq.ProvideController(db, provider, authorizer, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, gitrpcInterface, reporter, mutexManager, migrator) pullreqController := pullreq.ProvideController(db, provider, authorizer, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, gitrpcInterface, reporter, mutexManager, migrator)
webhookConfig, err := server.ProvideWebhookConfig() webhookConfig := server.ProvideWebhookConfig(config)
if err != nil {
return nil, err
}
webhookStore := database.ProvideWebhookStore(db) webhookStore := database.ProvideWebhookStore(db)
webhookExecutionStore := database.ProvideWebhookExecutionStore(db) webhookExecutionStore := database.ProvideWebhookExecutionStore(db)
readerFactory, err := events4.ProvideReaderFactory(eventsSystem) readerFactory, err := events4.ProvideReaderFactory(eventsSystem)
@ -236,7 +234,12 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
servicesServices := services.ProvideServices(webhookService, pullreqService, executor, jobScheduler) triggerConfig := server.ProvideTriggerConfig(config)
triggerService, err := trigger2.ProvideService(ctx, triggerConfig, readerFactory, eventsReaderFactory)
if err != nil {
return nil, err
}
servicesServices := services.ProvideServices(webhookService, pullreqService, triggerService, jobScheduler)
serverSystem := server.NewSystem(bootstrapBootstrap, serverServer, poller, grpcServer, cronManager, servicesServices) serverSystem := server.NewSystem(bootstrapBootstrap, serverServer, poller, grpcServer, cronManager, servicesServices)
return serverSystem, nil return serverSystem, nil
} }

View File

@ -43,9 +43,9 @@ func (f *ReaderFactory[R]) Launch(ctx context.Context,
// setup ctx with copied logger that has extra fields set // setup ctx with copied logger that has extra fields set
log := log.Ctx(ctx).With(). log := log.Ctx(ctx).With().
Str("events_category", f.category). Str("events.category", f.category).
Str("events_group_name", groupName). Str("events.group_name", groupName).
Str("events_reader_name", readerName). Str("events.reader_name", readerName).
Logger() Logger()
// create new stream consumer using factory method // create new stream consumer using factory method
@ -191,8 +191,8 @@ func ReaderRegisterEvent[T interface{}](reader *GenericReader,
// update ctx with event type for proper logging // update ctx with event type for proper logging
log := log.Ctx(ctx).With(). log := log.Ctx(ctx).With().
Str("events_type", string(eventType)). Str("events.type", string(eventType)).
Str("events_id", event.ID). Str("events.id", event.ID).
Logger() Logger()
ctx = log.WithContext(ctx) ctx = log.WithContext(ctx)

View File

@ -69,6 +69,7 @@ func (g Adapter) GetDefaultBranch(ctx context.Context, repoPath string) (string,
// If the repo doesn't have a default branch, types.ErrNoDefaultBranch is returned. // If the repo doesn't have a default branch, types.ErrNoDefaultBranch is returned.
func (g Adapter) GetRemoteDefaultBranch(ctx context.Context, remoteURL string) (string, error) { func (g Adapter) GetRemoteDefaultBranch(ctx context.Context, remoteURL string) (string, error) {
args := []string{ args := []string{
"-c", "credential.helper=",
"ls-remote", "ls-remote",
"--symref", "--symref",
"-q", "-q",
@ -118,6 +119,7 @@ func (g Adapter) Clone(ctx context.Context, from, to string, opts types.CloneRep
func (g Adapter) Sync(ctx context.Context, repoPath string, remoteURL string) error { func (g Adapter) Sync(ctx context.Context, repoPath string, remoteURL string) error {
args := []string{ args := []string{
"-c", "advice.fetchShowForcedUpdates=false", "-c", "advice.fetchShowForcedUpdates=false",
"-c", "credential.helper=",
"fetch", "fetch",
"--quiet", "--quiet",
"--prune", "--prune",
@ -131,7 +133,8 @@ func (g Adapter) Sync(ctx context.Context, repoPath string, remoteURL string) er
cmd := gitea.NewCommand(ctx, args...) cmd := gitea.NewCommand(ctx, args...)
_, _, err := cmd.RunStdString(&gitea.RunOpts{ _, _, err := cmd.RunStdString(&gitea.RunOpts{
Dir: repoPath, Dir: repoPath,
UseContextTimeout: true,
}) })
if err != nil { if err != nil {
return processGiteaErrorf(err, "failed to sync repo") return processGiteaErrorf(err, "failed to sync repo")

View File

@ -344,6 +344,11 @@ func (s RepositoryService) SyncRepository(
// get remote default branch // get remote default branch
defaultBranch, err := s.adapter.GetRemoteDefaultBranch(ctx, request.GetSource()) defaultBranch, err := s.adapter.GetRemoteDefaultBranch(ctx, request.GetSource())
if errors.Is(err, types.ErrNoDefaultBranch) {
return &rpc.SyncRepositoryResponse{
DefaultBranch: "",
}, nil
}
if err != nil { if err != nil {
return nil, processGitErrorf(err, "failed to get default branch from repo") return nil, processGitErrorf(err, "failed to get default branch from repo")
} }

View File

@ -9,14 +9,14 @@ import (
"strings" "strings"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
func (c *Controller) Blame(ctx context.Context, session *auth.Session, func (c *Controller) Blame(ctx context.Context,
session *auth.Session,
repoRef, gitRef, path string, repoRef, gitRef, path string,
lineFrom, lineTo int, lineFrom, lineTo int,
) (types.Stream[*gitrpc.BlamePart], error) { ) (types.Stream[*gitrpc.BlamePart], error) {
@ -29,15 +29,11 @@ func (c *Controller) Blame(ctx context.Context, session *auth.Session,
return nil, usererror.BadRequest("Line range must be valid.") return nil, usererror.BadRequest("Line range must be valid.")
} }
repo, err := c.repoStore.FindByRef(ctx, repoRef) repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, true); err != nil {
return nil, err
}
if gitRef == "" { if gitRef == "" {
gitRef = repo.DefaultBranch gitRef = repo.DefaultBranch
} }

View File

@ -11,7 +11,6 @@ import (
"time" "time"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/internal/bootstrap" "github.com/harness/gitness/internal/bootstrap"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
@ -40,17 +39,16 @@ type CommitFilesResponse struct {
CommitID string `json:"commit_id"` CommitID string `json:"commit_id"`
} }
func (c *Controller) CommitFiles(ctx context.Context, session *auth.Session, func (c *Controller) CommitFiles(ctx context.Context,
repoRef string, in *CommitFilesOptions) (CommitFilesResponse, error) { session *auth.Session,
repo, err := c.repoStore.FindByRef(ctx, repoRef) repoRef string,
in *CommitFilesOptions,
) (CommitFilesResponse, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush, false)
if err != nil { if err != nil {
return CommitFilesResponse{}, err return CommitFilesResponse{}, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoPush, false); err != nil {
return CommitFilesResponse{}, err
}
actions := make([]gitrpc.CommitFileAction, len(in.Actions)) actions := make([]gitrpc.CommitFileAction, len(in.Actions))
for i, action := range in.Actions { for i, action := range in.Actions {
var rawPayload []byte var rawPayload []byte

View File

@ -11,7 +11,6 @@ import (
"io" "io"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/api/controller" "github.com/harness/gitness/internal/api/controller"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
@ -89,15 +88,11 @@ func (c *Controller) GetContent(ctx context.Context,
repoPath string, repoPath string,
includeLatestCommit bool, includeLatestCommit bool,
) (*GetContentOutput, error) { ) (*GetContentOutput, error) {
repo, err := c.repoStore.FindByRef(ctx, repoRef) repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, true); err != nil {
return nil, err
}
// set gitRef to default branch in case an empty reference was provided // set gitRef to default branch in case an empty reference was provided
if gitRef == "" { if gitRef == "" {
gitRef = repo.DefaultBranch gitRef = repo.DefaultBranch

View File

@ -8,7 +8,6 @@ import (
"context" "context"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
@ -30,21 +29,19 @@ func (c *Controller) PathsDetails(ctx context.Context,
gitRef string, gitRef string,
input PathsDetailsInput, input PathsDetailsInput,
) (PathsDetailsOutput, error) { ) (PathsDetailsOutput, error) {
repo, err := c.repoStore.FindByRef(ctx, repoRef) repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil { if err != nil {
return PathsDetailsOutput{}, err return PathsDetailsOutput{}, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, true); err != nil {
return PathsDetailsOutput{}, err
}
if len(input.Paths) == 0 { if len(input.Paths) == 0 {
return PathsDetailsOutput{}, nil return PathsDetailsOutput{}, nil
} }
if len(input.Paths) > 50 { const maxInputPaths = 50
return PathsDetailsOutput{}, usererror.BadRequest("maximum number of elements in the Paths array is 25") if len(input.Paths) > maxInputPaths {
return PathsDetailsOutput{},
usererror.BadRequestf("maximum number of elements in the Paths array is %d", maxInputPaths)
} }
// set gitRef to default branch in case an empty reference was provided // set gitRef to default branch in case an empty reference was provided

View File

@ -12,6 +12,8 @@ import (
"strings" "strings"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
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/auth"
"github.com/harness/gitness/internal/auth/authz" "github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/githook" "github.com/harness/gitness/internal/githook"
@ -20,6 +22,7 @@ import (
"github.com/harness/gitness/internal/url" "github.com/harness/gitness/internal/url"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
"github.com/harness/gitness/types/check" "github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )
@ -70,6 +73,35 @@ func NewController(
} }
} }
// getRepoCheckAccess fetches an active repo (not one that is currently being imported)
// and checks if the current user has permission to access it.
func (c *Controller) getRepoCheckAccess(
ctx context.Context,
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
orPublic bool,
) (*types.Repository, error) {
if repoRef == "" {
return nil, usererror.BadRequest("A valid repository reference must be provided.")
}
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repository: %w", err)
}
if repo.Importing {
return nil, usererror.BadRequest("Repository import is in progress.")
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission, orPublic); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}
return repo, nil
}
// CreateRPCWriteParams creates base write parameters for gitrpc write operations. // CreateRPCWriteParams creates base write parameters for gitrpc write operations.
// IMPORTANT: session & repo are assumed to be not nil! // IMPORTANT: session & repo are assumed to be not nil!
func CreateRPCWriteParams(ctx context.Context, urlProvider *url.Provider, func CreateRPCWriteParams(ctx context.Context, urlProvider *url.Provider,

View File

@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
@ -24,17 +23,16 @@ type CreateBranchInput struct {
} }
// CreateBranch creates a new branch for a repo. // CreateBranch creates a new branch for a repo.
func (c *Controller) CreateBranch(ctx context.Context, session *auth.Session, func (c *Controller) CreateBranch(ctx context.Context,
repoRef string, in *CreateBranchInput) (*Branch, error) { session *auth.Session,
repo, err := c.repoStore.FindByRef(ctx, repoRef) repoRef string,
in *CreateBranchInput,
) (*Branch, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoPush, false); err != nil {
return nil, err
}
// set target to default branch in case no target was provided // set target to default branch in case no target was provided
if in.Target == "" { if in.Target == "" {
in.Target = repo.DefaultBranch in.Target = repo.DefaultBranch

View File

@ -10,7 +10,6 @@ import (
"time" "time"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
@ -28,17 +27,16 @@ type CreateCommitTagInput struct {
} }
// CreateCommitTag creates a new tag for a repo. // CreateCommitTag creates a new tag for a repo.
func (c *Controller) CreateCommitTag(ctx context.Context, session *auth.Session, func (c *Controller) CreateCommitTag(ctx context.Context,
repoRef string, in *CreateCommitTagInput) (*CommitTag, error) { session *auth.Session,
repo, err := c.repoStore.FindByRef(ctx, repoRef) repoRef string,
in *CreateCommitTagInput,
) (*CommitTag, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoPush, false); err != nil {
return nil, err
}
// set target to default branch in case no branch or commit was provided // set target to default branch in case no branch or commit was provided
if in.Target == "" { if in.Target == "" {
in.Target = repo.DefaultBranch in.Target = repo.DefaultBranch

View File

@ -10,7 +10,6 @@ import (
"strings" "strings"
"time" "time"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
@ -24,17 +23,16 @@ type CreatePathInput struct {
} }
// CreatePath creates a new path for a repo. // CreatePath creates a new path for a repo.
func (c *Controller) CreatePath(ctx context.Context, session *auth.Session, func (c *Controller) CreatePath(ctx context.Context,
repoRef string, in *CreatePathInput) (*types.Path, error) { session *auth.Session,
repo, err := c.repoStore.FindByRef(ctx, repoRef) repoRef string,
in *CreatePathInput,
) (*types.Path, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoEdit, false); err != nil {
return nil, err
}
if err = c.sanitizeCreatePathInput(in, repo.Path); err != nil { if err = c.sanitizeCreatePathInput(in, repo.Path); err != nil {
return nil, fmt.Errorf("failed to sanitize input: %w", err) return nil, fmt.Errorf("failed to sanitize input: %w", err)
} }

View File

@ -19,6 +19,7 @@ import (
// Delete deletes a repo. // Delete deletes a repo.
func (c *Controller) Delete(ctx context.Context, session *auth.Session, repoRef string) error { func (c *Controller) Delete(ctx context.Context, session *auth.Session, repoRef string) error {
// note: can't use c.getRepoCheckAccess because import job for repositories being imported must be cancelled.
repo, err := c.repoStore.FindByRef(ctx, repoRef) repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil { if err != nil {
return err return err
@ -27,9 +28,21 @@ func (c *Controller) Delete(ctx context.Context, session *auth.Session, repoRef
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoDelete, false); err != nil { if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoDelete, false); err != nil {
return err return err
} }
if repo.Importing {
err = c.importer.Cancel(ctx, repo)
if err != nil {
return fmt.Errorf("failed to cancel repository import")
}
return c.DeleteNoAuth(ctx, session, repo)
}
log.Ctx(ctx).Info().Msgf("Delete request received for repo %s , id: %d", repo.Path, repo.ID) log.Ctx(ctx).Info().Msgf("Delete request received for repo %s , id: %d", repo.Path, repo.ID)
// TODO: uncomment when soft delete is implemented // TODO: uncomment when soft delete is implemented
// return c.DeleteNoAuth(ctx, session, repo) // return c.DeleteNoAuth(ctx, session, repo)
return nil return nil
} }

View File

@ -9,23 +9,22 @@ import (
"fmt" "fmt"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
// DeleteBranch deletes a repo branch. // DeleteBranch deletes a repo branch.
func (c *Controller) DeleteBranch(ctx context.Context, session *auth.Session, repoRef string, branchName string) error { func (c *Controller) DeleteBranch(ctx context.Context,
repo, err := c.repoStore.FindByRef(ctx, repoRef) session *auth.Session,
repoRef string,
branchName string,
) error {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush, false)
if err != nil { if err != nil {
return err return err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoPush, false); err != nil {
return err
}
// make sure user isn't deleting the default branch // make sure user isn't deleting the default branch
// ASSUMPTION: lower layer calls explicit branch api // ASSUMPTION: lower layer calls explicit branch api
// and 'refs/heads/branch1' would fail if 'branch1' exists. // and 'refs/heads/branch1' would fail if 'branch1' exists.

View File

@ -8,7 +8,6 @@ import (
"context" "context"
"fmt" "fmt"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/store/database/dbtx"
@ -17,16 +16,16 @@ import (
) )
// DeletePath deletes a repo path. // DeletePath deletes a repo path.
func (c *Controller) DeletePath(ctx context.Context, session *auth.Session, repoRef string, pathID int64) error { func (c *Controller) DeletePath(ctx context.Context,
repo, err := c.repoStore.FindByRef(ctx, repoRef) session *auth.Session,
repoRef string,
pathID int64,
) error {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit, false)
if err != nil { if err != nil {
return err return err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoEdit, false); err != nil {
return err
}
err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error { err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error {
var path *types.Path var path *types.Path
path, err = c.pathStore.FindWithLock(ctx, pathID) path, err = c.pathStore.FindWithLock(ctx, pathID)

View File

@ -9,23 +9,21 @@ import (
"fmt" "fmt"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
// DeleteTag deletes a tag from the repo. // DeleteTag deletes a tag from the repo.
func (c *Controller) DeleteTag(ctx context.Context, session *auth.Session, func (c *Controller) DeleteTag(ctx context.Context,
repoRef, tagName string) error { session *auth.Session,
repo, err := c.repoStore.FindByRef(ctx, repoRef) repoRef,
tagName string,
) error {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush, false)
if err != nil { if err != nil {
return err return err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoPush, false); err != nil {
return err
}
writeParams, err := CreateRPCWriteParams(ctx, c.urlProvider, session, repo) writeParams, err := CreateRPCWriteParams(ctx, c.urlProvider, session, repo)
if err != nil { if err != nil {
return fmt.Errorf("failed to create RPC write params: %w", err) return fmt.Errorf("failed to create RPC write params: %w", err)

View File

@ -25,15 +25,11 @@ func (c *Controller) RawDiff(
path string, path string,
w io.Writer, w io.Writer,
) error { ) error {
repo, err := c.repoStore.FindByRef(ctx, repoRef) repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil { if err != nil {
return err return err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
return err
}
info, err := parseDiffPath(path) info, err := parseDiffPath(path)
if err != nil { if err != nil {
return err return err

View File

@ -15,6 +15,7 @@ import (
// Find finds a repo. // Find finds a repo.
func (c *Controller) Find(ctx context.Context, session *auth.Session, repoRef string) (*types.Repository, error) { func (c *Controller) Find(ctx context.Context, session *auth.Session, repoRef string) (*types.Repository, error) {
// note: can't use c.getRepoCheckAccess because even repositories that are currently being imported can be fetched.
repo, err := c.repoStore.FindByRef(ctx, repoRef) repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil { if err != nil {
return nil, err return nil, err
@ -24,7 +25,7 @@ func (c *Controller) Find(ctx context.Context, session *auth.Session, repoRef st
return nil, err return nil, err
} }
// backfil clone url // backfill clone url
repo.GitURL = c.urlProvider.GenerateRepoCloneURL(repo.Path) repo.GitURL = c.urlProvider.GenerateRepoCloneURL(repo.Path)
return repo, nil return repo, nil

View File

@ -9,21 +9,19 @@ import (
"fmt" "fmt"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
// GetBranch gets a repo branch. // GetBranch gets a repo branch.
func (c *Controller) GetBranch(ctx context.Context, session *auth.Session, func (c *Controller) GetBranch(ctx context.Context,
repoRef string, branchName string) (*Branch, error) { session *auth.Session,
repo, err := c.repoStore.FindByRef(ctx, repoRef) repoRef string,
branchName string,
) (*Branch, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil { if err != nil {
return nil, fmt.Errorf("faild to find repo: %w", err) return nil, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
} }
rpcOut, err := c.gitRPCClient.GetBranch(ctx, &gitrpc.GetBranchParams{ rpcOut, err := c.gitRPCClient.GetBranch(ctx, &gitrpc.GetBranchParams{

View File

@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/api/controller" "github.com/harness/gitness/internal/api/controller"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
@ -17,15 +16,14 @@ import (
) )
// GetCommit gets a repo commit. // GetCommit gets a repo commit.
func (c *Controller) GetCommit(ctx context.Context, session *auth.Session, func (c *Controller) GetCommit(ctx context.Context,
repoRef string, sha string) (*types.Commit, error) { session *auth.Session,
repo, err := c.repoStore.FindByRef(ctx, repoRef) repoRef string,
sha string,
) (*types.Commit, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil { if err != nil {
return nil, fmt.Errorf("faild to find repo: %w", err) return nil, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
} }
rpcOut, err := c.gitRPCClient.GetCommit(ctx, &gitrpc.GetCommitParams{ rpcOut, err := c.gitRPCClient.GetCommit(ctx, &gitrpc.GetCommitParams{

View File

@ -8,7 +8,6 @@ import (
"context" "context"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/api/request" "github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
@ -39,20 +38,17 @@ type CommitDivergence struct {
Behind int32 `json:"behind"` Behind int32 `json:"behind"`
} }
/* // GetCommitDivergences returns the commit divergences between reference pairs.
* GetCommitDivergences returns the commit divergences between reference pairs. func (c *Controller) GetCommitDivergences(ctx context.Context,
*/ session *auth.Session,
func (c *Controller) GetCommitDivergences(ctx context.Context, session *auth.Session, repoRef string,
repoRef string, in *GetCommitDivergencesInput) ([]CommitDivergence, error) { in *GetCommitDivergencesInput,
repo, err := c.repoStore.FindByRef(ctx, repoRef) ) ([]CommitDivergence, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
return nil, err
}
// if no requests were provided return an empty list // if no requests were provided return an empty list
if in == nil || len(in.Requests) == 0 { if in == nil || len(in.Requests) == 0 {
return []CommitDivergence{}, nil return []CommitDivergence{}, nil

View File

@ -7,34 +7,23 @@ package repo
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/harness/gitness/gitrpc"
"github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/internal/bootstrap"
"github.com/harness/gitness/internal/githook"
"github.com/harness/gitness/internal/paths" "github.com/harness/gitness/internal/paths"
"github.com/harness/gitness/internal/services/importer" "github.com/harness/gitness/internal/services/importer"
"github.com/harness/gitness/internal/services/job" "github.com/harness/gitness/internal/services/job"
"github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
) )
type ImportInput struct { type ImportInput struct {
ParentRef string `json:"parent_ref"` ParentRef string `json:"parent_ref"`
UID string `json:"uid"` UID string `json:"uid"`
Provider importer.ProviderType `json:"provider"`
ProviderURL string `json:"provider_url"`
RepoSlug string `json:"repo_slug"`
Username string `json:"username"`
Password string `json:"password"`
Description string `json:"description"` Description string `json:"description"`
Provider importer.Provider `json:"provider"`
ProviderRepo string `json:"provider_repo"`
} }
// Import creates a new empty repository and starts git import to it from a remote repository. // Import creates a new empty repository and starts git import to it from a remote repository.
@ -49,14 +38,7 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo
return nil, fmt.Errorf("failed to sanitize input: %w", err) return nil, fmt.Errorf("failed to sanitize input: %w", err)
} }
providerInfo := importer.ProviderInfo{ remoteRepository, err := importer.LoadRepositoryFromProvider(ctx, in.Provider, in.ProviderRepo)
Type: in.Provider,
Host: in.ProviderURL,
User: in.Username,
Pass: in.Password,
}
repoInfo, err := importer.Repo(ctx, providerInfo, in.RepoSlug)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -66,29 +48,7 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo
return nil, fmt.Errorf("error creating job UID: %w", err) return nil, fmt.Errorf("error creating job UID: %w", err)
} }
gitRPCResp, err := c.createEmptyGitRepository(ctx, session) var repo *types.Repository
if err != nil {
return nil, fmt.Errorf("error creating repository on GitRPC: %w", err)
}
now := time.Now().UnixMilli()
repo := &types.Repository{
Version: 0,
ParentID: parentSpace.ID,
UID: in.UID,
GitUID: gitRPCResp.UID,
Path: "", // the path is set in the DB transaction below
Description: in.Description,
IsPublic: repoInfo.IsPublic,
CreatedBy: session.Principal.ID,
Created: now,
Updated: now,
ForkID: 0,
DefaultBranch: repoInfo.DefaultBranch,
Importing: true,
ImportingJobUID: &jobUID,
}
err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error { 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 // lock parent space path to ensure it doesn't get updated while we setup new repo
spacePath, err := c.pathStore.FindPrimaryWithLock(ctx, enum.PathTargetTypeSpace, parentSpace.ID) spacePath, err := c.pathStore.FindPrimaryWithLock(ctx, enum.PathTargetTypeSpace, parentSpace.ID)
@ -96,7 +56,8 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo
return usererror.BadRequest("Parent not found'") return usererror.BadRequest("Parent not found'")
} }
repo.Path = paths.Concatinate(spacePath.Value, in.UID) pathToRepo := paths.Concatinate(spacePath.Value, in.UID)
repo = remoteRepository.ToRepo(parentSpace.ID, pathToRepo, in.UID, in.Description, jobUID, &session.Principal)
err = c.repoStore.Create(ctx, repo) err = c.repoStore.Create(ctx, repo)
if err != nil { if err != nil {
@ -119,26 +80,17 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo
return fmt.Errorf("failed to create path: %w", err) return fmt.Errorf("failed to create path: %w", err)
} }
err = c.importer.Run(ctx, in.Provider, repo, remoteRepository.CloneURL)
if err != nil {
return fmt.Errorf("failed to start import repository job: %w", err)
}
return nil return nil
}) })
if err != nil { if err != nil {
if err := c.DeleteRepositoryRPC(ctx, session, repo); err != nil {
log.Ctx(ctx).Warn().Err(err).Msg("gitrpc failed to delete repo for cleanup")
}
return nil, err return nil, err
} }
err = c.importer.Run(ctx, jobUID, importer.Input{
RepoID: repo.ID,
GitUser: in.Username,
GitPass: in.Password,
CloneURL: repoInfo.CloneURL,
})
if err != nil {
log.Ctx(ctx).Err(err).Msg("failed to start import repository job")
}
repo.GitURL = c.urlProvider.GenerateRepoCloneURL(repo.Path) repo.GitURL = c.urlProvider.GenerateRepoCloneURL(repo.Path)
return repo, nil return repo, nil
@ -153,50 +105,5 @@ func (c *Controller) sanitizeImportInput(in *ImportInput) error {
return err return err
} }
if in.Provider == "" {
return usererror.BadRequest("provider must be provided")
}
if in.RepoSlug == "" {
return usererror.BadRequest("repo slug must be provided")
}
return nil return nil
} }
func (c *Controller) createEmptyGitRepository(
ctx context.Context,
session *auth.Session,
) (*gitrpc.CreateRepositoryOutput, error) {
// generate envars (add everything githook CLI needs for execution)
envVars, err := githook.GenerateEnvironmentVariables(
ctx,
c.urlProvider.GetAPIBaseURLInternal(),
0,
session.Principal.ID,
true,
)
if err != nil {
return nil, fmt.Errorf("failed to generate git hook environment variables: %w", err)
}
actor := rpcIdentityFromPrincipal(session.Principal)
committer := rpcIdentityFromPrincipal(bootstrap.NewSystemServiceSession().Principal)
now := time.Now()
resp, err := c.gitRPCClient.CreateRepository(ctx, &gitrpc.CreateRepositoryParams{
Actor: *actor,
EnvVars: envVars,
DefaultBranch: c.defaultBranch,
Files: nil,
Author: actor,
AuthorDate: &now,
Committer: committer,
CommitterDate: &now,
})
if err != nil {
return nil, fmt.Errorf("failed to create repo on gitrpc: %w", err)
}
return resp, nil
}

View File

@ -0,0 +1,41 @@
// 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 repo
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/enum"
)
// ImportCancel cancels a repository import.
func (c *Controller) ImportCancel(ctx context.Context,
session *auth.Session,
repoRef string,
) error {
// note: can't use c.getRepoCheckAccess because this needs to fetch a repo being imported.
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoDelete, false); err != nil {
return err
}
if !repo.Importing {
return usererror.BadRequest("repository is not being imported")
}
if err = c.importer.Cancel(ctx, repo); err != nil {
return fmt.Errorf("failed to cancel repository import")
}
return c.DeleteNoAuth(ctx, session, repo)
}

View File

@ -18,6 +18,7 @@ func (c *Controller) ImportProgress(ctx context.Context,
session *auth.Session, session *auth.Session,
repoRef string, repoRef string,
) (types.JobProgress, error) { ) (types.JobProgress, error) {
// note: can't use c.getRepoCheckAccess because this needs to fetch a repo being imported.
repo, err := c.repoStore.FindByRef(ctx, repoRef) repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil { if err != nil {
return types.JobProgress{}, err return types.JobProgress{}, err

View File

@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/api/controller" "github.com/harness/gitness/internal/api/controller"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
@ -22,20 +21,18 @@ type Branch struct {
Commit *types.Commit `json:"commit,omitempty"` Commit *types.Commit `json:"commit,omitempty"`
} }
/* // ListBranches lists the branches of a repo.
* ListBranches lists the branches of a repo. func (c *Controller) ListBranches(ctx context.Context,
*/ session *auth.Session,
func (c *Controller) ListBranches(ctx context.Context, session *auth.Session, repoRef string,
repoRef string, includeCommit bool, filter *types.BranchFilter) ([]Branch, error) { includeCommit bool,
repo, err := c.repoStore.FindByRef(ctx, repoRef) filter *types.BranchFilter,
) ([]Branch, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
return nil, err
}
rpcOut, err := c.gitRPCClient.ListBranches(ctx, &gitrpc.ListBranchesParams{ rpcOut, err := c.gitRPCClient.ListBranches(ctx, &gitrpc.ListBranchesParams{
ReadParams: CreateRPCReadParams(repo), ReadParams: CreateRPCReadParams(repo),
IncludeCommit: includeCommit, IncludeCommit: includeCommit,

View File

@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/api/controller" "github.com/harness/gitness/internal/api/controller"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
@ -26,20 +25,18 @@ type CommitTag struct {
Commit *types.Commit `json:"commit,omitempty"` Commit *types.Commit `json:"commit,omitempty"`
} }
/* // ListCommitTags lists the commit tags of a repo.
* ListCommitTags lists the commit tags of a repo. func (c *Controller) ListCommitTags(ctx context.Context,
*/ session *auth.Session,
func (c *Controller) ListCommitTags(ctx context.Context, session *auth.Session, repoRef string,
repoRef string, includeCommit bool, filter *types.TagFilter) ([]CommitTag, error) { includeCommit bool,
repo, err := c.repoStore.FindByRef(ctx, repoRef) filter *types.TagFilter,
) ([]CommitTag, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
return nil, err
}
rpcOut, err := c.gitRPCClient.ListCommitTags(ctx, &gitrpc.ListCommitTagsParams{ rpcOut, err := c.gitRPCClient.ListCommitTags(ctx, &gitrpc.ListCommitTagsParams{
ReadParams: CreateRPCReadParams(repo), ReadParams: CreateRPCReadParams(repo),
IncludeCommit: includeCommit, IncludeCommit: includeCommit,

View File

@ -9,27 +9,24 @@ import (
"fmt" "fmt"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/api/controller" "github.com/harness/gitness/internal/api/controller"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
/* // ListCommits lists the commits of a repo.
* ListCommits lists the commits of a repo. func (c *Controller) ListCommits(ctx context.Context,
*/ session *auth.Session,
func (c *Controller) ListCommits(ctx context.Context, session *auth.Session, repoRef string,
repoRef string, gitRef string, filter *types.CommitFilter) (types.ListCommitResponse, error) { gitRef string,
repo, err := c.repoStore.FindByRef(ctx, repoRef) filter *types.CommitFilter,
) (types.ListCommitResponse, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil { if err != nil {
return types.ListCommitResponse{}, err return types.ListCommitResponse{}, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
return types.ListCommitResponse{}, err
}
// set gitRef to default branch in case an empty reference was provided // set gitRef to default branch in case an empty reference was provided
if gitRef == "" { if gitRef == "" {
gitRef = repo.DefaultBranch gitRef = repo.DefaultBranch

View File

@ -8,27 +8,23 @@ import (
"context" "context"
"fmt" "fmt"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
/* // ListPaths lists all paths of a repo.
* ListPaths lists all paths of a repo. func (c *Controller) ListPaths(ctx context.Context,
*/ session *auth.Session,
func (c *Controller) ListPaths(ctx context.Context, session *auth.Session, repoRef string,
repoRef string, filter *types.PathFilter) ([]*types.Path, int64, error) { filter *types.PathFilter,
repo, err := c.repoStore.FindByRef(ctx, repoRef) ) ([]*types.Path, int64, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
return nil, 0, err
}
var ( var (
paths []*types.Path paths []*types.Path
count int64 count int64

View File

@ -1,13 +1,13 @@
// Copyright 2022 Harness Inc. All rights reserved. // Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License // 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. // that can be found in the LICENSE.md file for this repository.
package repo package repo
import ( import (
"context" "context"
"fmt" "fmt"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
@ -22,14 +22,9 @@ func (c *Controller) ListPipelines(
latest bool, latest bool,
filter types.ListQueryFilter, filter types.ListQueryFilter,
) ([]*types.Pipeline, int64, error) { ) ([]*types.Pipeline, int64, error) {
repo, err := c.repoStore.FindByRef(ctx, repoRef) repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil { if err != nil {
return nil, 0, fmt.Errorf("failed to find repo: %w", err) return nil, 0, err
}
err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionPipelineView, false)
if err != nil {
return nil, 0, fmt.Errorf("failed to authorize: %w", err)
} }
var count int64 var count int64

View File

@ -7,23 +7,19 @@ package repo
import ( import (
"context" "context"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
/* // ListServiceAccounts lists the service accounts of a repo.
* ListServiceAccounts lists the service accounts of a repo.
*/
func (c *Controller) ListServiceAccounts(ctx context.Context, session *auth.Session,
repoRef string) ([]*types.ServiceAccount, error) {
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return nil, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil { func (c *Controller) ListServiceAccounts(ctx context.Context,
session *auth.Session,
repoRef string,
) ([]*types.ServiceAccount, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, false)
if err != nil {
return nil, err return nil, err
} }

View File

@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
@ -25,15 +24,11 @@ func (c *Controller) MergeCheck(
repoRef string, repoRef string,
diffPath string, diffPath string,
) (MergeCheck, error) { ) (MergeCheck, error) {
repo, err := c.repoStore.FindByRef(ctx, repoRef) repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, false)
if err != nil { if err != nil {
return MergeCheck{}, err return MergeCheck{}, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
return MergeCheck{}, fmt.Errorf("access check failed: %w", err)
}
info, err := parseDiffPath(diffPath) info, err := parseDiffPath(diffPath)
if err != nil { if err != nil {
return MergeCheck{}, err return MergeCheck{}, err

View File

@ -7,6 +7,7 @@ package repo
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/harness/gitness/internal/api/usererror"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -55,13 +56,20 @@ func (i *MoveInput) hasChanges(repo *types.Repository) bool {
// Move moves a repository to a new space and/or uid. // Move moves a repository to a new space and/or uid.
// //
//nolint:gocognit // refactor if needed //nolint:gocognit // refactor if needed
func (c *Controller) Move(ctx context.Context, session *auth.Session, func (c *Controller) Move(ctx context.Context,
repoRef string, in *MoveInput) (*types.Repository, error) { session *auth.Session,
repoRef string,
in *MoveInput,
) (*types.Repository, error) {
repo, err := c.repoStore.FindByRef(ctx, repoRef) repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if repo.Importing {
return nil, usererror.BadRequest("can't move a repo that is being imported")
}
permission := enum.PermissionRepoEdit permission := enum.PermissionRepoEdit
var inParentSpaceID *int64 var inParentSpaceID *int64
if in.ParentRef != nil { if in.ParentRef != nil {

View File

@ -10,27 +10,24 @@ import (
"io" "io"
"github.com/harness/gitness/gitrpc" "github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
/* // Raw finds the file of the repo at the given path and returns its raw content.
* Raw finds the file of the repo at the given path and returns its raw content. // If no gitRef is provided, the content is retrieved from the default branch.
* If no gitRef is provided, the content is retrieved from the default branch. func (c *Controller) Raw(ctx context.Context,
*/ session *auth.Session,
func (c *Controller) Raw(ctx context.Context, session *auth.Session, repoRef string, repoRef string,
gitRef string, repoPath string) (io.Reader, int64, error) { gitRef string,
repo, err := c.repoStore.FindByRef(ctx, repoRef) repoPath string,
) (io.Reader, int64, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, true); err != nil {
return nil, 0, err
}
// set gitRef to default branch in case an empty reference was provided // set gitRef to default branch in case an empty reference was provided
if gitRef == "" { if gitRef == "" {
gitRef = repo.DefaultBranch gitRef = repo.DefaultBranch

View File

@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"strings" "strings"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
"github.com/harness/gitness/types/check" "github.com/harness/gitness/types/check"
@ -28,17 +27,16 @@ func (in *UpdateInput) hasChanges(repo *types.Repository) bool {
} }
// Update updates a repository. // Update updates a repository.
func (c *Controller) Update(ctx context.Context, session *auth.Session, func (c *Controller) Update(ctx context.Context,
repoRef string, in *UpdateInput) (*types.Repository, error) { session *auth.Session,
repo, err := c.repoStore.FindByRef(ctx, repoRef) repoRef string,
in *UpdateInput,
) (*types.Repository, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoEdit, false); err != nil {
return nil, err
}
if !in.hasChanges(repo) { if !in.hasChanges(repo) {
return repo, nil return repo, nil
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/harness/gitness/internal/api/controller/repo" "github.com/harness/gitness/internal/api/controller/repo"
"github.com/harness/gitness/internal/auth/authz" "github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/pipeline/events" "github.com/harness/gitness/internal/pipeline/events"
"github.com/harness/gitness/internal/services/importer"
"github.com/harness/gitness/internal/store" "github.com/harness/gitness/internal/store"
"github.com/harness/gitness/internal/url" "github.com/harness/gitness/internal/url"
"github.com/harness/gitness/types/check" "github.com/harness/gitness/types/check"
@ -31,6 +32,7 @@ type Controller struct {
principalStore store.PrincipalStore principalStore store.PrincipalStore
repoCtrl *repo.Controller repoCtrl *repo.Controller
membershipStore store.MembershipStore membershipStore store.MembershipStore
importer *importer.Repository
} }
func NewController(db *sqlx.DB, urlProvider *url.Provider, eventsStream events.EventsStreamer, func NewController(db *sqlx.DB, urlProvider *url.Provider, eventsStream events.EventsStreamer,
@ -38,7 +40,7 @@ func NewController(db *sqlx.DB, urlProvider *url.Provider, eventsStream events.E
pathStore store.PathStore, pipelineStore store.PipelineStore, secretStore store.SecretStore, pathStore store.PathStore, pipelineStore store.PipelineStore, secretStore store.SecretStore,
connectorStore store.ConnectorStore, templateStore store.TemplateStore, spaceStore store.SpaceStore, connectorStore store.ConnectorStore, templateStore store.TemplateStore, spaceStore store.SpaceStore,
repoStore store.RepoStore, principalStore store.PrincipalStore, repoCtrl *repo.Controller, repoStore store.RepoStore, principalStore store.PrincipalStore, repoCtrl *repo.Controller,
membershipStore store.MembershipStore, membershipStore store.MembershipStore, importer *importer.Repository,
) *Controller { ) *Controller {
return &Controller{ return &Controller{
db: db, db: db,
@ -56,5 +58,6 @@ func NewController(db *sqlx.DB, urlProvider *url.Provider, eventsStream events.E
principalStore: principalStore, principalStore: principalStore,
repoCtrl: repoCtrl, repoCtrl: repoCtrl,
membershipStore: membershipStore, membershipStore: membershipStore,
importer: importer,
} }
} }

View File

@ -0,0 +1,183 @@
// 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"
"strconv"
"strings"
"time"
"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/internal/services/importer"
"github.com/harness/gitness/internal/services/job"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
)
type ImportInput struct {
CreateInput
Provider importer.Provider `json:"provider"`
ProviderSpace string `json:"provider_space"`
}
// Import creates new space and starts import of all repositories from the remote provider's space into it.
func (c *Controller) Import(ctx context.Context, session *auth.Session, in *ImportInput) (*types.Space, error) {
parentSpace, err := c.getSpaceCheckAuthSpaceCreation(ctx, session, in.ParentRef)
if err != nil {
return nil, err
}
if in.UID == "" {
in.UID = in.ProviderSpace
}
err = c.sanitizeCreateInput(&in.CreateInput)
if err != nil {
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
remoteRepositories, err := importer.LoadRepositoriesFromProviderSpace(ctx, in.Provider, in.ProviderSpace)
if err != nil {
return nil, err
}
if len(remoteRepositories) == 0 {
return nil, usererror.BadRequestf("found no repositories at %s", in.ProviderSpace)
}
localRepositories := make([]*types.Repository, len(remoteRepositories))
cloneURLs := make([]string, len(remoteRepositories))
var space *types.Space
err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error {
spacePath := in.UID
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, parentSpaceID)
if err != nil {
return usererror.BadRequest("Parent not found")
}
spacePath = paths.Concatinate(parentPath.Value, in.UID)
// ensure path is within accepted depth!
err = check.PathDepth(spacePath, true)
if err != nil {
return fmt.Errorf("path is invalid: %w", err)
}
}
now := time.Now().UnixMilli()
space = &types.Space{
Version: 0,
ParentID: parentSpaceID,
UID: in.UID,
Path: spacePath,
Description: in.Description,
IsPublic: in.IsPublic,
CreatedBy: session.Principal.ID,
Created: now,
Updated: now,
}
err = c.spaceStore.Create(ctx, space)
if err != nil {
return fmt.Errorf("space creation failed: %w", err)
}
path := &types.Path{
Version: 0,
Value: space.Path,
IsPrimary: true,
TargetType: enum.PathTargetTypeSpace,
TargetID: space.ID,
CreatedBy: space.CreatedBy,
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)
}
}
for i, remoteRepository := range remoteRepositories {
var jobUID string
jobUID, err = job.UID()
if err != nil {
return fmt.Errorf("error creating job UID: %w", err)
}
pathToRepo := paths.Concatinate(path.Value, remoteRepository.UID)
repo := remoteRepository.ToRepo(
space.ID, pathToRepo, remoteRepository.UID, "", jobUID, &session.Principal)
err = c.repoStore.Create(ctx, repo)
if err != nil {
return fmt.Errorf("failed to create repository in storage: %w", err)
}
repoPath := &types.Path{
Version: 0,
Value: repo.Path,
IsPrimary: true,
TargetType: enum.PathTargetTypeRepo,
TargetID: repo.ID,
CreatedBy: repo.CreatedBy,
Created: repo.Created,
Updated: repo.Updated,
}
err = c.pathStore.Create(ctx, repoPath)
if err != nil {
return fmt.Errorf("failed to create path: %w", err)
}
localRepositories[i] = repo
cloneURLs[i] = remoteRepository.CloneURL
}
jobGroupID := fmt.Sprintf("space-import-%d", space.ID)
err = c.importer.RunMany(ctx, jobGroupID, in.Provider, localRepositories, cloneURLs)
if err != nil {
return fmt.Errorf("failed to start import repository jobs: %w", err)
}
return nil
})
if err != nil {
return nil, err
}
return space, nil
}

View File

@ -8,6 +8,7 @@ import (
"github.com/harness/gitness/internal/api/controller/repo" "github.com/harness/gitness/internal/api/controller/repo"
"github.com/harness/gitness/internal/auth/authz" "github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/pipeline/events" "github.com/harness/gitness/internal/pipeline/events"
"github.com/harness/gitness/internal/services/importer"
"github.com/harness/gitness/internal/store" "github.com/harness/gitness/internal/store"
"github.com/harness/gitness/internal/url" "github.com/harness/gitness/internal/url"
"github.com/harness/gitness/types/check" "github.com/harness/gitness/types/check"
@ -26,9 +27,11 @@ func ProvideController(db *sqlx.DB, urlProvider *url.Provider, eventsStream even
pipelineStore store.PipelineStore, secretStore store.SecretStore, pipelineStore store.PipelineStore, secretStore store.SecretStore,
connectorStore store.ConnectorStore, templateStore store.TemplateStore, connectorStore store.ConnectorStore, templateStore store.TemplateStore,
spaceStore store.SpaceStore, repoStore store.RepoStore, principalStore store.PrincipalStore, spaceStore store.SpaceStore, repoStore store.RepoStore, principalStore store.PrincipalStore,
repoCtrl *repo.Controller, membershipStore store.MembershipStore, repoCtrl *repo.Controller, membershipStore store.MembershipStore, importer *importer.Repository,
) *Controller { ) *Controller {
return NewController(db, urlProvider, eventsStream, uidCheck, authorizer, return NewController(db, urlProvider, eventsStream, uidCheck, authorizer,
pathStore, pipelineStore, secretStore, connectorStore, templateStore, pathStore, pipelineStore, secretStore,
spaceStore, repoStore, principalStore, repoCtrl, membershipStore) connectorStore, templateStore,
spaceStore, repoStore, principalStore,
repoCtrl, membershipStore, importer)
} }

View File

@ -0,0 +1,33 @@
// 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 repo
import (
"net/http"
"github.com/harness/gitness/internal/api/controller/repo"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
)
func HandleImportCancel(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return
}
err = repoCtrl.ImportCancel(ctx, session, repoRef)
if err != nil {
render.TranslatedUserError(w, err)
return
}
render.DeleteSuccessful(w)
}
}

View File

@ -0,0 +1,36 @@
// 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"
)
func HandleImport(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
in := new(space.ImportInput)
err := json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(w, "Invalid Request Body: %s.", err)
return
}
space, err := spaceCtrl.Import(ctx, session, in)
if err != nil {
render.TranslatedUserError(w, err)
return
}
render.JSON(w, http.StatusCreated, space)
}
}

View File

@ -384,6 +384,18 @@ func repoOperations(reflector *openapi3.Reflector) {
_ = reflector.SetJSONResponse(&createRepository, new(usererror.Error), http.StatusForbidden) _ = reflector.SetJSONResponse(&createRepository, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost, "/repos", createRepository) _ = reflector.Spec.AddOperation(http.MethodPost, "/repos", createRepository)
importRepository := openapi3.Operation{}
importRepository.WithTags("repository")
importRepository.WithMapOfAnything(map[string]interface{}{"operationId": "importRepository"})
importRepository.WithParameters(queryParameterSpacePath)
_ = reflector.SetRequest(&importRepository, &struct{ repo.ImportInput }{}, http.MethodPost)
_ = reflector.SetJSONResponse(&importRepository, new(types.Repository), http.StatusCreated)
_ = reflector.SetJSONResponse(&importRepository, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&importRepository, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&importRepository, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&importRepository, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost, "/repos/import", importRepository)
opFind := openapi3.Operation{} opFind := openapi3.Operation{}
opFind.WithTags("repository") opFind.WithTags("repository")
opFind.WithMapOfAnything(map[string]interface{}{"operationId": "findRepository"}) opFind.WithMapOfAnything(map[string]interface{}{"operationId": "findRepository"})

View File

@ -158,6 +158,17 @@ func spaceOperations(reflector *openapi3.Reflector) {
_ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusForbidden) _ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost, "/spaces", opCreate) _ = reflector.Spec.AddOperation(http.MethodPost, "/spaces", opCreate)
opImport := openapi3.Operation{}
opImport.WithTags("space")
opImport.WithMapOfAnything(map[string]interface{}{"operationId": "importSpace"})
_ = reflector.SetRequest(&opImport, &struct{ space.ImportInput }{}, http.MethodPost)
_ = reflector.SetJSONResponse(&opImport, new(types.Space), http.StatusCreated)
_ = reflector.SetJSONResponse(&opImport, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opImport, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opImport, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opImport, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost, "/spaces/import", opImport)
opGet := openapi3.Operation{} opGet := openapi3.Operation{}
opGet.WithTags("space") opGet.WithTags("space")
opGet.WithMapOfAnything(map[string]interface{}{"operationId": "getSpace"}) opGet.WithMapOfAnything(map[string]interface{}{"operationId": "getSpace"})

View File

@ -181,6 +181,7 @@ func setupSpaces(r chi.Router, spaceCtrl *space.Controller) {
r.Route("/spaces", func(r chi.Router) { r.Route("/spaces", func(r chi.Router) {
// Create takes path and parentId via body, not uri // Create takes path and parentId via body, not uri
r.Post("/", handlerspace.HandleCreate(spaceCtrl)) r.Post("/", handlerspace.HandleCreate(spaceCtrl))
r.Post("/import", handlerspace.HandleImport(spaceCtrl))
r.Route(fmt.Sprintf("/{%s}", request.PathParamSpaceRef), func(r chi.Router) { r.Route(fmt.Sprintf("/{%s}", request.PathParamSpaceRef), func(r chi.Router) {
// space operations // space operations

View File

@ -9,25 +9,40 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"time"
"github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/types"
"github.com/drone/go-scm/scm" "github.com/drone/go-scm/scm"
"github.com/drone/go-scm/scm/driver/github" "github.com/drone/go-scm/scm/driver/github"
"github.com/drone/go-scm/scm/driver/gitlab"
"github.com/drone/go-scm/scm/transport/oauth2" "github.com/drone/go-scm/scm/transport/oauth2"
) )
type ProviderType string type ProviderType string
const ( const (
ProviderTypeGitHub ProviderType = "github" ProviderTypeGitHub ProviderType = "github"
ProviderTypeGitHubEnterprise ProviderType = "github-enterprise"
ProviderTypeGitLab ProviderType = "gitlab"
ProviderTypeGitLabEnterprise ProviderType = "gitlab-enterprise"
) )
type ProviderInfo struct { func (p ProviderType) Enum() []any {
Type ProviderType return []any{
Host string ProviderTypeGitHub,
User string ProviderTypeGitHubEnterprise,
Pass string ProviderTypeGitLab,
ProviderTypeGitLabEnterprise,
}
}
type Provider struct {
Type ProviderType `json:"type"`
Host string `json:"host"`
Username string `json:"username"`
Password string `json:"password"`
} }
type RepositoryInfo struct { type RepositoryInfo struct {
@ -38,42 +53,120 @@ type RepositoryInfo struct {
DefaultBranch string DefaultBranch string
} }
func getClient(provider ProviderInfo) (*scm.Client, error) { // ToRepo converts the RepositoryInfo into the types.Repository object marked as being imported.
var scmClient *scm.Client func (r *RepositoryInfo) ToRepo(
spaceID int64,
path string,
uid string,
description string,
jobUID string,
principal *types.Principal,
) *types.Repository {
now := time.Now().UnixMilli()
gitTempUID := "importing-" + jobUID
return &types.Repository{
Version: 0,
ParentID: spaceID,
UID: uid,
GitUID: gitTempUID, // the correct git UID will be set by the job handler
Path: path,
Description: description,
IsPublic: r.IsPublic,
CreatedBy: principal.ID,
Created: now,
Updated: now,
ForkID: 0,
DefaultBranch: r.DefaultBranch,
Importing: true,
ImportingJobUID: &jobUID,
}
}
func getClient(provider Provider) (*scm.Client, error) {
switch provider.Type { switch provider.Type {
case "": case "":
return nil, usererror.BadRequest("provider can not be empty") return nil, usererror.BadRequest("provider can not be empty")
case ProviderTypeGitHub: case ProviderTypeGitHub:
scmClient = github.NewDefault() c := github.NewDefault()
if provider.Pass != "" { if provider.Password != "" {
scmClient.Client = &http.Client{ c.Client = &http.Client{
Transport: &oauth2.Transport{ Transport: &oauth2.Transport{
Source: oauth2.StaticTokenSource(&scm.Token{Token: provider.Pass}), Source: oauth2.StaticTokenSource(&scm.Token{Token: provider.Password}),
}, },
} }
} }
return c, nil
case ProviderTypeGitHubEnterprise:
c, err := github.New(provider.Host)
if err != nil {
return nil, usererror.BadRequestf("provider Host invalid: %s", err.Error())
}
if provider.Password != "" {
c.Client = &http.Client{
Transport: &oauth2.Transport{
Source: oauth2.StaticTokenSource(&scm.Token{Token: provider.Password}),
},
}
}
return c, nil
case ProviderTypeGitLab:
c := gitlab.NewDefault()
if provider.Password != "" {
c.Client = &http.Client{
Transport: &oauth2.Transport{
Source: oauth2.StaticTokenSource(&scm.Token{Token: provider.Password}),
},
}
}
return c, nil
case ProviderTypeGitLabEnterprise:
c, err := gitlab.New(provider.Host)
if err != nil {
return nil, usererror.BadRequestf("provider Host invalid: %s", err.Error())
}
if provider.Password != "" {
c.Client = &http.Client{
Transport: &oauth2.Transport{
Source: oauth2.StaticTokenSource(&scm.Token{Token: provider.Password}),
},
}
}
return c, nil
default: default:
return nil, usererror.BadRequestf("unsupported provider: %s", provider) return nil, usererror.BadRequestf("unsupported provider: %s", provider)
} }
return scmClient, nil
} }
func Repo(ctx context.Context, provider ProviderInfo, repoSlug string) (RepositoryInfo, error) { func LoadRepositoryFromProvider(ctx context.Context, provider Provider, repoSlug string) (RepositoryInfo, error) {
scmClient, err := getClient(provider) scmClient, err := getClient(provider)
if err != nil { if err != nil {
return RepositoryInfo{}, err return RepositoryInfo{}, err
} }
if repoSlug == "" {
return RepositoryInfo{}, usererror.BadRequest("provider repository identifier is missing")
}
scmRepo, _, err := scmClient.Repositories.Find(ctx, repoSlug) scmRepo, _, err := scmClient.Repositories.Find(ctx, repoSlug)
if errors.Is(err, scm.ErrNotFound) { if errors.Is(err, scm.ErrNotFound) {
return RepositoryInfo{}, usererror.BadRequestf("repository %s not found at %s", repoSlug, provider) return RepositoryInfo{},
usererror.BadRequestf("repository %s not found at %s", repoSlug, provider.Type)
} }
if errors.Is(err, scm.ErrNotAuthorized) { if errors.Is(err, scm.ErrNotAuthorized) {
return RepositoryInfo{}, usererror.BadRequestf("bad credentials provided for %s at %s", repoSlug, provider) return RepositoryInfo{},
usererror.BadRequestf("bad credentials provided for %s at %s", repoSlug, provider.Type)
} }
if err != nil { if err != nil {
return RepositoryInfo{}, fmt.Errorf("failed to fetch repository %s from %s: %w", repoSlug, provider, err) return RepositoryInfo{},
fmt.Errorf("failed to fetch repository %s from %s: %w", repoSlug, provider.Type, err)
} }
return RepositoryInfo{ return RepositoryInfo{
@ -85,13 +178,17 @@ func Repo(ctx context.Context, provider ProviderInfo, repoSlug string) (Reposito
}, nil }, nil
} }
func Space(ctx context.Context, provider ProviderInfo, space string) (map[string]RepositoryInfo, error) { func LoadRepositoriesFromProviderSpace(ctx context.Context, provider Provider, spaceSlug string) ([]RepositoryInfo, error) {
scmClient, err := getClient(provider) scmClient, err := getClient(provider)
if err != nil { if err != nil {
return nil, err return nil, err
} }
repoMap := make(map[string]RepositoryInfo) if spaceSlug == "" {
return nil, usererror.BadRequest("provider space identifier is missing")
}
repos := make([]RepositoryInfo, 0)
const pageSize = 50 const pageSize = 50
page := 1 page := 1
@ -104,31 +201,27 @@ func Space(ctx context.Context, provider ProviderInfo, space string) (map[string
}, },
RepoSearchTerm: scm.RepoSearchTerm{ RepoSearchTerm: scm.RepoSearchTerm{
RepoName: "", RepoName: "",
User: space, User: spaceSlug,
}, },
}) })
if errors.Is(err, scm.ErrNotFound) { if errors.Is(err, scm.ErrNotFound) {
return nil, usererror.BadRequestf("space %s not found at %s", space, provider) return nil, usererror.BadRequestf("space %s not found at %s", spaceSlug, provider.Type)
} }
if errors.Is(err, scm.ErrNotAuthorized) { if errors.Is(err, scm.ErrNotAuthorized) {
return nil, usererror.BadRequestf("bad credentials provided for %s at %s", space, provider) return nil, usererror.BadRequestf("bad credentials provided for %s at %s", spaceSlug, provider.Type)
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch space %s from %s: %w", space, provider, err) return nil, fmt.Errorf("failed to fetch space %s from %s: %w", spaceSlug, provider.Type, err)
} }
for _, scmRepo := range scmRepos { for _, scmRepo := range scmRepos {
if !scmRepo.Perm.Pull { repos = append(repos, RepositoryInfo{
continue
}
repoMap[scmRepo.Name] = RepositoryInfo{
Space: scmRepo.Namespace, Space: scmRepo.Namespace,
UID: scmRepo.Name, UID: scmRepo.Name,
CloneURL: scmRepo.Clone, CloneURL: scmRepo.Clone,
IsPublic: !scmRepo.Private, IsPublic: !scmRepo.Private,
DefaultBranch: scmRepo.Branch, DefaultBranch: scmRepo.Branch,
} })
} }
if len(scmRepos) == 0 || page == scmResponse.Page.Last { if len(scmRepos) == 0 || page == scmResponse.Page.Last {
@ -138,5 +231,5 @@ func Space(ctx context.Context, provider ProviderInfo, space string) (map[string
page++ page++
} }
return repoMap, nil return repos, nil
} }

View File

@ -21,13 +21,21 @@ import (
gitnessurl "github.com/harness/gitness/internal/url" gitnessurl "github.com/harness/gitness/internal/url"
gitness_store "github.com/harness/gitness/store" gitness_store "github.com/harness/gitness/store"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
"github.com/rs/zerolog/log"
)
const (
importJobMaxRetries = 0
importJobMaxDuration = 45 * time.Minute
) )
type Repository struct { type Repository struct {
urlProvider *gitnessurl.Provider defaultBranch string
git gitrpc.Interface urlProvider *gitnessurl.Provider
repoStore store.RepoStore git gitrpc.Interface
scheduler *job.Scheduler repoStore store.RepoStore
scheduler *job.Scheduler
} }
var _ job.Handler = (*Repository)(nil) var _ job.Handler = (*Repository)(nil)
@ -41,29 +49,90 @@ type Input struct {
const jobType = "repository_import" const jobType = "repository_import"
func (i *Repository) Register(executor *job.Executor) error { func (r *Repository) Register(executor *job.Executor) error {
return executor.Register(jobType, i) return executor.Register(jobType, r)
} }
func (i *Repository) Run(ctx context.Context, jobUID string, input Input) error { // Run starts a background job that imports the provided repository from the provided clone URL.
func (r *Repository) Run(ctx context.Context, provider Provider, repo *types.Repository, cloneURL string) error {
input := Input{
RepoID: repo.ID,
GitUser: provider.Username,
GitPass: provider.Password,
CloneURL: cloneURL,
}
data, err := json.Marshal(input) data, err := json.Marshal(input)
if err != nil { if err != nil {
return err return fmt.Errorf("failed to marshal job input json: %w", err)
} }
strData := strings.TrimSpace(string(data)) strData := strings.TrimSpace(string(data))
return i.scheduler.RunJob(ctx, job.Definition{ return r.scheduler.RunJob(ctx, job.Definition{
UID: jobUID, UID: *repo.ImportingJobUID,
Type: jobType, Type: jobType,
MaxRetries: 1, MaxRetries: importJobMaxRetries,
Timeout: 30 * time.Minute, Timeout: importJobMaxDuration,
Data: strData, Data: strData,
}) })
} }
// RunMany starts background jobs that import the provided repositories from the provided clone URLs.
func (r *Repository) RunMany(ctx context.Context,
groupID string,
provider Provider,
repos []*types.Repository,
cloneURLs []string,
) error {
if len(repos) != len(cloneURLs) {
return fmt.Errorf("slice length mismatch: have %d repositories and %d clone URLs",
len(repos), len(cloneURLs))
}
n := len(repos)
defs := make([]job.Definition, n)
for k := 0; k < n; k++ {
repo := repos[k]
cloneURL := cloneURLs[k]
input := Input{
RepoID: repo.ID,
GitUser: provider.Username,
GitPass: provider.Password,
CloneURL: cloneURL,
}
data, err := json.Marshal(input)
if err != nil {
return fmt.Errorf("failed to marshal job input json: %w", err)
}
strData := strings.TrimSpace(string(data))
defs[k] = job.Definition{
UID: *repo.ImportingJobUID,
Type: jobType,
MaxRetries: importJobMaxRetries,
Timeout: importJobMaxDuration,
Data: strData,
}
}
err := r.scheduler.RunJobs(ctx, groupID, defs)
if err != nil {
return fmt.Errorf("failed to run jobs: %w", err)
}
return nil
}
// Handle is repository import background job handler. // Handle is repository import background job handler.
func (i *Repository) Handle(ctx context.Context, data string, _ job.ProgressReporter) (string, error) { func (r *Repository) Handle(ctx context.Context, data string, _ job.ProgressReporter) (string, error) {
systemPrincipal := bootstrap.NewSystemServiceSession().Principal
var input Input var input Input
if err := json.NewDecoder(strings.NewReader(data)).Decode(&input); err != nil { if err := json.NewDecoder(strings.NewReader(data)).Decode(&input); err != nil {
return "", fmt.Errorf("failed to unmarshal job input: %w", err) return "", fmt.Errorf("failed to unmarshal job input: %w", err)
@ -83,7 +152,7 @@ func (i *Repository) Handle(ctx context.Context, data string, _ job.ProgressRepo
input.CloneURL = repoURL.String() input.CloneURL = repoURL.String()
} }
repo, err := i.repoStore.Find(ctx, input.RepoID) repo, err := r.repoStore.Find(ctx, input.RepoID)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to find repo by id: %w", err) return "", fmt.Errorf("failed to find repo by id: %w", err)
} }
@ -92,38 +161,60 @@ func (i *Repository) Handle(ctx context.Context, data string, _ job.ProgressRepo
return "", fmt.Errorf("repository %s is not being imported", repo.UID) return "", fmt.Errorf("repository %s is not being imported", repo.UID)
} }
writeParams, err := createRPCWriteParams(ctx, i.urlProvider, repo) gitUID, err := r.createGitRepository(ctx, &systemPrincipal, repo.ID)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to create write params: %w", err) return "", fmt.Errorf("failed to create empty git repository: %w", err)
} }
syncOut, err := i.git.SyncRepository(ctx, &gitrpc.SyncRepositoryParams{ err = func() error {
WriteParams: writeParams, repo.GitUID = gitUID
Source: input.CloneURL,
CreateIfNotExists: false, defaultBranch, err := r.syncGitRepository(ctx, &systemPrincipal, repo, input.CloneURL)
}) if err != nil {
return fmt.Errorf("failed to sync git repository from '%s': %w", input.CloneURL, err)
}
if defaultBranch == "" {
defaultBranch = r.defaultBranch
}
repo, err = r.repoStore.UpdateOptLock(ctx, repo, func(repo *types.Repository) error {
if !repo.Importing {
return errors.New("repository has already finished importing")
}
repo.GitUID = gitUID
repo.DefaultBranch = defaultBranch
repo.Importing = false
return nil
})
if err != nil {
return fmt.Errorf("failed to update repository after import: %w", err)
}
return nil
}()
if err != nil { if err != nil {
return "", fmt.Errorf("failed to sync repositories: %w", err) if errDel := r.deleteGitRepository(ctx, &systemPrincipal, repo); errDel != nil {
log.Ctx(ctx).Err(err).
Str("gitUID", gitUID).
Msg("failed to delete git repository after failed import")
}
return "", fmt.Errorf("failed to import repository: %w", err)
} }
repo.Importing = false return "", nil
repo.DefaultBranch = syncOut.DefaultBranch
err = i.repoStore.Update(ctx, repo)
if err != nil {
return "", fmt.Errorf("failed to update repository after import: %w", err)
}
return "", err
} }
func (i *Repository) GetProgress(ctx context.Context, repo *types.Repository) (types.JobProgress, error) { func (r *Repository) GetProgress(ctx context.Context, repo *types.Repository) (types.JobProgress, error) {
if !repo.Importing || repo.ImportingJobUID == nil || *repo.ImportingJobUID == "" { if !repo.Importing || repo.ImportingJobUID == nil || *repo.ImportingJobUID == "" {
// if the repo is not being imported, or it's job ID has been cleared (or never existed) return state=finished // if the repo is not being imported, or it's job ID has been cleared (or never existed) return state=finished
return job.DoneProgress(), nil return job.DoneProgress(), nil
} }
progress, err := i.scheduler.GetJobProgress(ctx, *repo.ImportingJobUID) progress, err := r.scheduler.GetJobProgress(ctx, *repo.ImportingJobUID)
if errors.Is(err, gitness_store.ErrResourceNotFound) { if errors.Is(err, gitness_store.ErrResourceNotFound) {
// if the job is not found return state=failed // if the job is not found return state=failed
return job.FailProgress(), nil return job.FailProgress(), nil
@ -135,31 +226,130 @@ func (i *Repository) GetProgress(ctx context.Context, repo *types.Repository) (t
return progress, nil return progress, nil
} }
// CreateRPCWriteParams creates base write parameters for gitrpc write operations. func (r *Repository) Cancel(ctx context.Context, repo *types.Repository) error {
func createRPCWriteParams(ctx context.Context, if repo.ImportingJobUID == nil || *repo.ImportingJobUID == "" {
urlProvider *gitnessurl.Provider, return nil
}
err := r.scheduler.CancelJob(ctx, *repo.ImportingJobUID)
if err != nil {
return fmt.Errorf("failed to cancel job: %w", err)
}
return nil
}
func (r *Repository) createGitRepository(ctx context.Context,
principal *types.Principal,
repoID int64,
) (string, error) {
now := time.Now()
envVars, err := r.createEnvVars(ctx, principal, repoID)
if err != nil {
return "", err
}
resp, err := r.git.CreateRepository(ctx, &gitrpc.CreateRepositoryParams{
Actor: gitrpc.Identity{
Name: principal.DisplayName,
Email: principal.Email,
},
EnvVars: envVars,
DefaultBranch: r.defaultBranch,
Files: nil,
Author: &gitrpc.Identity{
Name: principal.DisplayName,
Email: principal.Email,
},
AuthorDate: &now,
Committer: &gitrpc.Identity{
Name: principal.DisplayName,
Email: principal.Email,
},
CommitterDate: &now,
})
if err != nil {
return "", fmt.Errorf("failed to create empty git repository: %w", err)
}
return resp.UID, nil
}
func (r *Repository) syncGitRepository(ctx context.Context,
principal *types.Principal,
repo *types.Repository,
sourceCloneURL string,
) (string, error) {
writeParams, err := r.createRPCWriteParams(ctx, principal, repo)
if err != nil {
return "", err
}
syncOut, err := r.git.SyncRepository(ctx, &gitrpc.SyncRepositoryParams{
WriteParams: writeParams,
Source: sourceCloneURL,
CreateIfNotExists: false,
})
if err != nil {
return "", fmt.Errorf("failed to sync repository: %w", err)
}
return syncOut.DefaultBranch, nil
}
func (r *Repository) deleteGitRepository(ctx context.Context,
principal *types.Principal,
repo *types.Repository,
) error {
writeParams, err := r.createRPCWriteParams(ctx, principal, repo)
if err != nil {
return err
}
err = r.git.DeleteRepository(ctx, &gitrpc.DeleteRepositoryParams{
WriteParams: writeParams,
})
if err != nil {
return fmt.Errorf("failed to delete git repository: %w", err)
}
return nil
}
func (r *Repository) createRPCWriteParams(ctx context.Context,
principal *types.Principal,
repo *types.Repository, repo *types.Repository,
) (gitrpc.WriteParams, error) { ) (gitrpc.WriteParams, error) {
gitnessSession := bootstrap.NewSystemServiceSession() envVars, err := r.createEnvVars(ctx, principal, repo.ID)
// generate envars (add everything githook CLI needs for execution)
envVars, err := githook.GenerateEnvironmentVariables(
ctx,
urlProvider.GetAPIBaseURLInternal(),
repo.ID,
gitnessSession.Principal.ID,
false,
)
if err != nil { if err != nil {
return gitrpc.WriteParams{}, fmt.Errorf("failed to generate git hook environment variables: %w", err) return gitrpc.WriteParams{}, err
} }
return gitrpc.WriteParams{ return gitrpc.WriteParams{
Actor: gitrpc.Identity{ Actor: gitrpc.Identity{
Name: gitnessSession.Principal.DisplayName, Name: principal.DisplayName,
Email: gitnessSession.Principal.Email, Email: principal.Email,
}, },
RepoUID: repo.GitUID, RepoUID: repo.GitUID,
EnvVars: envVars, EnvVars: envVars,
}, nil }, nil
} }
func (r *Repository) createEnvVars(ctx context.Context,
principal *types.Principal,
repoID int64,
) (map[string]string, error) {
envVars, err := githook.GenerateEnvironmentVariables(
ctx,
r.urlProvider.GetAPIBaseURLInternal(),
repoID,
principal.ID,
false,
)
if err != nil {
return nil, fmt.Errorf("failed to generate git hook environment variables: %w", err)
}
return envVars, nil
}

View File

@ -9,6 +9,7 @@ import (
"github.com/harness/gitness/internal/services/job" "github.com/harness/gitness/internal/services/job"
"github.com/harness/gitness/internal/store" "github.com/harness/gitness/internal/store"
"github.com/harness/gitness/internal/url" "github.com/harness/gitness/internal/url"
"github.com/harness/gitness/types"
"github.com/google/wire" "github.com/google/wire"
) )
@ -18,6 +19,7 @@ var WireSet = wire.NewSet(
) )
func ProvideRepoImporter( func ProvideRepoImporter(
config *types.Config,
urlProvider *url.Provider, urlProvider *url.Provider,
git gitrpc.Interface, git gitrpc.Interface,
repoStore store.RepoStore, repoStore store.RepoStore,
@ -25,10 +27,11 @@ func ProvideRepoImporter(
executor *job.Executor, executor *job.Executor,
) (*Repository, error) { ) (*Repository, error) {
importer := &Repository{ importer := &Repository{
urlProvider: urlProvider, defaultBranch: config.Git.DefaultBranch,
git: git, urlProvider: urlProvider,
repoStore: repoStore, git: git,
scheduler: scheduler, repoStore: repoStore,
scheduler: scheduler,
} }
err := executor.Register(jobType, importer) err := executor.Register(jobType, importer)

View File

@ -36,7 +36,6 @@ type Scheduler struct {
purgeMinOldAge time.Duration purgeMinOldAge time.Duration
// synchronization stuff // synchronization stuff
globalCtx context.Context
signal chan time.Time signal chan time.Time
done chan struct{} done chan struct{}
wgRunning sync.WaitGroup wgRunning sync.WaitGroup
@ -102,7 +101,6 @@ func (s *Scheduler) Run(ctx context.Context) error {
defer close(s.done) defer close(s.done)
s.signal = make(chan time.Time, 1) s.signal = make(chan time.Time, 1)
s.globalCtx = ctx
timer := newSchedulerTimer() timer := newSchedulerTimer()
defer timer.Stop() defer timer.Stop()
@ -271,75 +269,41 @@ func (s *Scheduler) RunJob(ctx context.Context, def Definition) error {
return err return err
} }
return s.startNewJobs(ctx, []*types.Job{def.toNewJob()}) job := def.toNewJob()
if err := s.store.Create(ctx, job); err != nil {
return fmt.Errorf("failed to add new job to the database: %w", err)
}
s.scheduleProcessing(time.UnixMilli(job.Scheduled))
return nil
} }
// RunJobs runs a several jobs. It's more efficient than calling RunJob several times // RunJobs runs a several jobs. It's more efficient than calling RunJob several times
// because it locks the DB only once. // because it locks the DB only once.
// TODO: Add groupID parameter and use it for all jobs. func (s *Scheduler) RunJobs(ctx context.Context, groupID string, defs []Definition) error {
func (s *Scheduler) RunJobs(ctx context.Context, defs []Definition) error { if len(defs) == 0 {
return nil
}
jobs := make([]*types.Job, len(defs)) jobs := make([]*types.Job, len(defs))
for i, def := range defs { for i, def := range defs {
if err := def.Validate(); err != nil { if err := def.Validate(); err != nil {
return err return err
} }
jobs[i] = def.toNewJob() jobs[i] = def.toNewJob()
} jobs[i].GroupID = groupID
return s.startNewJobs(ctx, jobs)
}
func (s *Scheduler) startNewJobs(ctx context.Context, jobs []*types.Job) error {
mx, err := globalLock(ctx, s.mxManager)
if err != nil {
return fmt.Errorf("failed to obtain global lock to start new jobs: %w", err)
}
defer func() {
if err := mx.Unlock(ctx); err != nil {
log.Ctx(ctx).Err(err).Msg("failed to release global lock after starting new jobs")
}
}()
return s.startNewJobsNoLock(ctx, jobs)
}
func (s *Scheduler) startNewJobsNoLock(ctx context.Context, jobs []*types.Job) error {
available, err := s.availableSlots(ctx)
if err != nil {
return fmt.Errorf("failed to count available slots for job execution: %w", err)
} }
for _, job := range jobs { for _, job := range jobs {
if available > 0 { if err := s.store.Create(ctx, job); err != nil {
available--
s.preExec(job) // Update the job fields for the new execution: It will be added to the DB as "running".
}
err = s.store.Create(ctx, job)
if err != nil {
return fmt.Errorf("failed to add new job to the database: %w", err) return fmt.Errorf("failed to add new job to the database: %w", err)
} }
if job.State != enum.JobStateRunning {
continue
}
func(ctx context.Context) {
ctx = log.Ctx(ctx).With().
Str("job.UID", job.UID).
Str("job.Type", job.Type).
Logger().WithContext(ctx)
// tell everybody that a job has started
if err := publishStateChange(ctx, s.pubsubService, job); err != nil {
log.Err(err).Msg("failed to publish job state change")
}
s.runJob(ctx, job)
}(s.globalCtx)
} }
s.scheduleProcessing(time.Now())
return nil return nil
} }

View File

@ -0,0 +1,22 @@
// 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 trigger
import (
"context"
"github.com/harness/gitness/events"
gitevents "github.com/harness/gitness/internal/events/git"
)
func (s *Service) handleEventBranchCreated(ctx context.Context,
event *events.Event[*gitevents.BranchCreatedPayload]) error {
return events.NewDiscardEventErrorf("not implemented")
}
func (s *Service) handleEventBranchUpdated(ctx context.Context,
event *events.Event[*gitevents.BranchUpdatedPayload]) error {
return events.NewDiscardEventErrorf("not implemented")
}

View File

@ -0,0 +1,27 @@
// 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 trigger
import (
"context"
"github.com/harness/gitness/events"
pullreqevents "github.com/harness/gitness/internal/events/pullreq"
)
func (s *Service) handleEventPullReqCreated(ctx context.Context,
event *events.Event[*pullreqevents.CreatedPayload]) error {
return events.NewDiscardEventErrorf("not implemented")
}
func (s *Service) handleEventPullReqReopened(ctx context.Context,
event *events.Event[*pullreqevents.ReopenedPayload]) error {
return events.NewDiscardEventErrorf("not implemented")
}
func (s *Service) handleEventPullReqBranchUpdated(ctx context.Context,
event *events.Event[*pullreqevents.BranchUpdatedPayload]) error {
return events.NewDiscardEventErrorf("not implemented")
}

View File

@ -0,0 +1,22 @@
// 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 trigger
import (
"context"
"github.com/harness/gitness/events"
gitevents "github.com/harness/gitness/internal/events/git"
)
func (s *Service) handleEventTagCreated(ctx context.Context,
event *events.Event[*gitevents.TagCreatedPayload]) error {
return events.NewDiscardEventErrorf("not implemented")
}
func (s *Service) handleEventTagUpdated(ctx context.Context,
event *events.Event[*gitevents.TagUpdatedPayload]) error {
return events.NewDiscardEventErrorf("not implemented")
}

View File

@ -0,0 +1,103 @@
// 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 trigger
import (
"context"
"errors"
"fmt"
"time"
"github.com/harness/gitness/events"
gitevents "github.com/harness/gitness/internal/events/git"
pullreqevents "github.com/harness/gitness/internal/events/pullreq"
"github.com/harness/gitness/stream"
)
const (
eventsReaderGroupName = "gitness:trigger"
)
type Config struct {
EventReaderName string
Concurrency int
MaxRetries int
}
func (c *Config) Prepare() error {
if c == nil {
return errors.New("config is required")
}
if c.EventReaderName == "" {
return errors.New("config.EventReaderName is required")
}
if c.Concurrency < 1 {
return errors.New("config.Concurrency has to be a positive number")
}
if c.MaxRetries < 0 {
return errors.New("config.MaxRetries can't be negative")
}
return nil
}
type Service struct{}
func New(
ctx context.Context,
config Config,
gitReaderFactory *events.ReaderFactory[*gitevents.Reader],
pullreqEvReaderFactory *events.ReaderFactory[*pullreqevents.Reader],
) (*Service, error) {
if err := config.Prepare(); err != nil {
return nil, fmt.Errorf("provided trigger service config is invalid: %w", err)
}
service := &Service{}
_, err := gitReaderFactory.Launch(ctx, eventsReaderGroupName, config.EventReaderName,
func(r *gitevents.Reader) error {
const idleTimeout = 1 * time.Minute
r.Configure(
stream.WithConcurrency(config.Concurrency),
stream.WithHandlerOptions(
stream.WithIdleTimeout(idleTimeout),
stream.WithMaxRetries(config.MaxRetries),
))
_ = r.RegisterBranchCreated(service.handleEventBranchCreated)
_ = r.RegisterBranchUpdated(service.handleEventBranchUpdated)
_ = r.RegisterTagCreated(service.handleEventTagCreated)
_ = r.RegisterTagUpdated(service.handleEventTagUpdated)
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to launch git events reader: %w", err)
}
_, err = pullreqEvReaderFactory.Launch(ctx, eventsReaderGroupName, config.EventReaderName,
func(r *pullreqevents.Reader) error {
const idleTimeout = 1 * time.Minute
r.Configure(
stream.WithConcurrency(config.Concurrency),
stream.WithHandlerOptions(
stream.WithIdleTimeout(idleTimeout),
stream.WithMaxRetries(config.MaxRetries),
))
_ = r.RegisterCreated(service.handleEventPullReqCreated)
_ = r.RegisterBranchUpdated(service.handleEventPullReqBranchUpdated)
_ = r.RegisterReopened(service.handleEventPullReqReopened)
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to launch pr events reader: %w", err)
}
return service, nil
}

View File

@ -0,0 +1,28 @@
// 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 trigger
import (
"context"
"github.com/harness/gitness/events"
gitevents "github.com/harness/gitness/internal/events/git"
pullreqevents "github.com/harness/gitness/internal/events/pullreq"
"github.com/google/wire"
)
var WireSet = wire.NewSet(
ProvideService,
)
func ProvideService(
ctx context.Context,
config Config,
gitReaderFactory *events.ReaderFactory[*gitevents.Reader],
pullReqEvFactory *events.ReaderFactory[*pullreqevents.Reader],
) (*Service, error) {
return New(ctx, config, gitReaderFactory, pullReqEvFactory)
}

View File

@ -27,17 +27,15 @@ const (
type Config struct { type Config struct {
// UserAgentIdentity specifies the identity used for the user agent header // UserAgentIdentity specifies the identity used for the user agent header
// IMPORTANT: do not include version. // IMPORTANT: do not include version.
UserAgentIdentity string `envconfig:"GITNESS_WEBHOOK_USER_AGENT_IDENTITY" default:"Gitness"` UserAgentIdentity string
// HeaderIdentity specifies the identity used for headers in webhook calls (e.g. X-Gitness-Trigger, ...). // HeaderIdentity specifies the identity used for headers in webhook calls (e.g. X-Gitness-Trigger, ...).
// NOTE: If no value is provided, the UserAgentIdentity will be used. // NOTE: If no value is provided, the UserAgentIdentity will be used.
HeaderIdentity string `envconfig:"GITNESS_WEBHOOK_HEADER_IDENTITY"` HeaderIdentity string
// EventReaderName is the name used to read events from stream. EventReaderName string
// Note: this should be different for every running instance. Concurrency int
EventReaderName string `envconfig:"GITNESS_WEBHOOK_EVENT_READER_NAME"` MaxRetries int
Concurrency int `envconfig:"GITNESS_WEBHOOK_CONCURRENCY" default:"4"` AllowPrivateNetwork bool
MaxRetries int `envconfig:"GITNESS_WEBHOOK_MAX_RETRIES" default:"3"` AllowLoopback bool
AllowPrivateNetwork bool `envconfig:"GITNESS_WEBHOOK_ALLOW_PRIVATE_NETWORK" default:"false"`
AllowLoopback bool `envconfig:"GITNESS_WEBHOOK_ALLOW_LOOPBACK" default:"false"`
} }
func (c *Config) Prepare() error { func (c *Config) Prepare() error {
@ -91,7 +89,7 @@ func NewService(ctx context.Context, config Config,
repoStore store.RepoStore, pullreqStore store.PullReqStore, urlProvider *url.Provider, repoStore store.RepoStore, pullreqStore store.PullReqStore, urlProvider *url.Provider,
principalStore store.PrincipalStore, gitRPCClient gitrpc.Interface) (*Service, error) { principalStore store.PrincipalStore, gitRPCClient gitrpc.Interface) (*Service, error) {
if err := config.Prepare(); err != nil { if err := config.Prepare(); err != nil {
return nil, fmt.Errorf("provided config is invalid: %w", err) return nil, fmt.Errorf("provided webhook service config is invalid: %w", err)
} }
service := &Service{ service := &Service{
webhookStore: webhookStore, webhookStore: webhookStore,

View File

@ -7,6 +7,7 @@ package services
import ( import (
"github.com/harness/gitness/internal/services/job" "github.com/harness/gitness/internal/services/job"
"github.com/harness/gitness/internal/services/pullreq" "github.com/harness/gitness/internal/services/pullreq"
"github.com/harness/gitness/internal/services/trigger"
"github.com/harness/gitness/internal/services/webhook" "github.com/harness/gitness/internal/services/webhook"
"github.com/google/wire" "github.com/google/wire"
@ -19,20 +20,20 @@ var WireSet = wire.NewSet(
type Services struct { type Services struct {
Webhook *webhook.Service Webhook *webhook.Service
PullReq *pullreq.Service PullReq *pullreq.Service
JobExecutor *job.Executor Trigger *trigger.Service
JobScheduler *job.Scheduler JobScheduler *job.Scheduler
} }
func ProvideServices( func ProvideServices(
webhooksSrv *webhook.Service, webhooksSvc *webhook.Service,
pullReqSrv *pullreq.Service, pullReqSvc *pullreq.Service,
jobExecutor *job.Executor, triggerSvc *trigger.Service,
jobScheduler *job.Scheduler, jobScheduler *job.Scheduler,
) Services { ) Services {
return Services{ return Services{
Webhook: webhooksSrv, Webhook: webhooksSvc,
PullReq: pullReqSrv, PullReq: pullReqSvc,
JobExecutor: jobExecutor, Trigger: triggerSvc,
JobScheduler: jobScheduler, JobScheduler: jobScheduler,
} }
} }

View File

@ -54,7 +54,8 @@ const (
,job_is_recurring ,job_is_recurring
,job_recurring_cron ,job_recurring_cron
,job_consecutive_failures ,job_consecutive_failures
,job_last_failure_error` ,job_last_failure_error
,job_group_id`
jobSelectBase = ` jobSelectBase = `
SELECT` + jobColumns + ` SELECT` + jobColumns + `
@ -101,6 +102,7 @@ func (s *JobStore) Create(ctx context.Context, job *types.Job) error {
,:job_recurring_cron ,:job_recurring_cron
,:job_consecutive_failures ,:job_consecutive_failures
,:job_last_failure_error ,:job_last_failure_error
,:job_group_id
)` )`
db := dbtx.GetAccessor(ctx, s.db) db := dbtx.GetAccessor(ctx, s.db)
@ -143,6 +145,7 @@ func (s *JobStore) Upsert(ctx context.Context, job *types.Job) error {
,:job_recurring_cron ,:job_recurring_cron
,:job_consecutive_failures ,:job_consecutive_failures
,:job_last_failure_error ,:job_last_failure_error
,:job_group_id
) )
ON CONFLICT (job_uid) DO ON CONFLICT (job_uid) DO
UPDATE SET UPDATE SET
@ -196,6 +199,7 @@ func (s *JobStore) UpdateDefinition(ctx context.Context, job *types.Job) error {
,job_scheduled = :job_scheduled ,job_scheduled = :job_scheduled
,job_is_recurring = :job_is_recurring ,job_is_recurring = :job_is_recurring
,job_recurring_cron = :job_recurring_cron ,job_recurring_cron = :job_recurring_cron
,job_group_id = :job_group_id
WHERE job_uid = :job_uid` WHERE job_uid = :job_uid`
db := dbtx.GetAccessor(ctx, s.db) db := dbtx.GetAccessor(ctx, s.db)

View File

@ -0,0 +1 @@
ALTER TABLE jobs DROP COLUMN job_group_id;

View File

@ -0,0 +1 @@
ALTER TABLE jobs ADD COLUMN job_group_id TEXT NOT NULL DEFAULT '';

View File

@ -0,0 +1 @@
ALTER TABLE jobs DROP COLUMN job_group_id;

View File

@ -0,0 +1 @@
ALTER TABLE jobs ADD COLUMN job_group_id TEXT NOT NULL DEFAULT '';

View File

@ -170,21 +170,22 @@ func (s *RepoStore) Update(ctx context.Context, repo *types.Repository) error {
const sqlQuery = ` const sqlQuery = `
UPDATE repositories UPDATE repositories
SET SET
repo_version = :repo_version repo_version = :repo_version
,repo_updated = :repo_updated ,repo_updated = :repo_updated
,repo_parent_id = :repo_parent_id ,repo_parent_id = :repo_parent_id
,repo_uid = :repo_uid ,repo_uid = :repo_uid
,repo_description = :repo_description ,repo_git_uid = :repo_git_uid
,repo_is_public = :repo_is_public ,repo_description = :repo_description
,repo_default_branch = :repo_default_branch ,repo_is_public = :repo_is_public
,repo_pullreq_seq = :repo_pullreq_seq ,repo_default_branch = :repo_default_branch
,repo_num_forks = :repo_num_forks ,repo_pullreq_seq = :repo_pullreq_seq
,repo_num_pulls = :repo_num_pulls ,repo_num_forks = :repo_num_forks
,repo_num_closed_pulls = :repo_num_closed_pulls ,repo_num_pulls = :repo_num_pulls
,repo_num_open_pulls = :repo_num_open_pulls ,repo_num_closed_pulls = :repo_num_closed_pulls
,repo_num_merged_pulls = :repo_num_merged_pulls ,repo_num_open_pulls = :repo_num_open_pulls
,repo_importing = :repo_importing ,repo_num_merged_pulls = :repo_num_merged_pulls
,repo_importing_job_uid = :repo_importing_job_uid ,repo_importing = :repo_importing
,repo_importing_job_uid = :repo_importing_job_uid
WHERE repo_id = :repo_id AND repo_version = :repo_version - 1` WHERE repo_id = :repo_id AND repo_version = :repo_version - 1`
updatedAt := time.Now() updatedAt := time.Now()
@ -219,7 +220,8 @@ func (s *RepoStore) Update(ctx context.Context, repo *types.Repository) error {
// UpdateOptLock updates the repository using the optimistic locking mechanism. // UpdateOptLock updates the repository using the optimistic locking mechanism.
func (s *RepoStore) UpdateOptLock(ctx context.Context, func (s *RepoStore) UpdateOptLock(ctx context.Context,
repo *types.Repository, repo *types.Repository,
mutateFn func(repository *types.Repository) error) (*types.Repository, error) { mutateFn func(repository *types.Repository) error,
) (*types.Repository, error) {
for { for {
dup := *repo dup := *repo

View File

@ -200,4 +200,22 @@ type Config struct {
// finished and failed jobs will be purged from the DB. // finished and failed jobs will be purged from the DB.
PurgeFinishedOlderThan time.Duration `envconfig:"GITNESS_JOBS_PURGE_FINISHED_OLDER_THAN" default:"120h"` PurgeFinishedOlderThan time.Duration `envconfig:"GITNESS_JOBS_PURGE_FINISHED_OLDER_THAN" default:"120h"`
} }
Webhook struct {
// UserAgentIdentity specifies the identity used for the user agent header
// IMPORTANT: do not include version.
UserAgentIdentity string `envconfig:"GITNESS_WEBHOOK_USER_AGENT_IDENTITY" default:"Gitness"`
// HeaderIdentity specifies the identity used for headers in webhook calls (e.g. X-Gitness-Trigger, ...).
// NOTE: If no value is provided, the UserAgentIdentity will be used.
HeaderIdentity string `envconfig:"GITNESS_WEBHOOK_HEADER_IDENTITY"`
Concurrency int `envconfig:"GITNESS_WEBHOOK_CONCURRENCY" default:"4"`
MaxRetries int `envconfig:"GITNESS_WEBHOOK_MAX_RETRIES" default:"3"`
AllowPrivateNetwork bool `envconfig:"GITNESS_WEBHOOK_ALLOW_PRIVATE_NETWORK" default:"false"`
AllowLoopback bool `envconfig:"GITNESS_WEBHOOK_ALLOW_LOOPBACK" default:"false"`
}
Trigger struct {
Concurrency int `envconfig:"GITNESS_TRIGGER_CONCURRENCY" default:"4"`
MaxRetries int `envconfig:"GITNESS_TRIGGER_MAX_RETRIES" default:"3"`
}
} }

View File

@ -27,6 +27,7 @@ type Job struct {
RecurringCron string `db:"job_recurring_cron"` RecurringCron string `db:"job_recurring_cron"`
ConsecutiveFailures int `db:"job_consecutive_failures"` ConsecutiveFailures int `db:"job_consecutive_failures"`
LastFailureError string `db:"job_last_failure_error"` LastFailureError string `db:"job_last_failure_error"`
GroupID string `db:"job_group_id"`
} }
type JobStateChange struct { type JobStateChange struct {

View File

@ -15,7 +15,9 @@
}, },
"keywords": [], "keywords": [],
"scripts": { "scripts": {
"dev": "NODE_ENV=development webpack serve --config config/webpack.dev.js", "webpack": "NODE_ENV=development webpack serve --config config/webpack.dev.js",
"typed-scss": "typed-scss-modules src --watch",
"dev": "run-p webpack typed-scss",
"test": "jest src --silent", "test": "jest src --silent",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"build": "rm -rf dist && webpack --config config/webpack.prod.js", "build": "rm -rf dist && webpack --config config/webpack.prod.js",
@ -62,7 +64,7 @@
"masonry-layout": "^4.2.2", "masonry-layout": "^4.2.2",
"moment": "^2.25.3", "moment": "^2.25.3",
"monaco-editor": "^0.40.0", "monaco-editor": "^0.40.0",
"monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-webpack-plugin": "^7.1.0",
"qs": "^6.9.4", "qs": "^6.9.4",
"react": "^17.0.2", "react": "^17.0.2",
"react-complex-tree": "^1.1.11", "react-complex-tree": "^1.1.11",
@ -104,7 +106,7 @@
"@types/qs": "^6.9.4", "@types/qs": "^6.9.4",
"@types/react": "^17.0.3", "@types/react": "^17.0.3",
"@types/react-dom": "^17.0.3", "@types/react-dom": "^17.0.3",
"@types/react-router-dom": "^5.1.7", "@types/react-router-dom": "^5.2.1",
"@types/react-table": "^7.0.18", "@types/react-table": "^7.0.18",
"@types/react-timeago": "^4.1.1", "@types/react-timeago": "^4.1.1",
"@types/testing-library__react-hooks": "^3.2.0", "@types/testing-library__react-hooks": "^3.2.0",
@ -143,6 +145,7 @@
"ts-jest": "^26.5.5", "ts-jest": "^26.5.5",
"ts-loader": "^9.2.6", "ts-loader": "^9.2.6",
"tsconfig-paths-webpack-plugin": "^3.5.1", "tsconfig-paths-webpack-plugin": "^3.5.1",
"typed-scss-modules": "^7.1.4",
"typescript": "^4.7.4", "typescript": "^4.7.4",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"webpack": "^5.58.0", "webpack": "^5.58.0",

View File

@ -1,7 +1,4 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const fullPage: string
readonly main: string export declare const main: string
readonly fullPage: string
}
export default styles

3
web/src/bootstrap.scss.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
/* eslint-disable */
// This is an auto-generated file
export declare const reactRoot: string

View File

@ -1,16 +1,13 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const card: string
readonly layout: string export declare const cardChildren: string
readonly cardColumn: string export declare const cardColumn: string
readonly card: string export declare const disclaimer: string
readonly cardChildren: string export declare const image: string
readonly imageColumn: string export declare const imageColumn: string
readonly image: string export declare const imageContainer: string
readonly subtractContainer: string export declare const layout: string
readonly subtractImage: string export declare const overlayImage: string
readonly imageContainer: string export declare const subtractContainer: string
readonly overlayImage: string export declare const subtractImage: string
readonly disclaimer: string
}
export default styles

View File

@ -1,14 +1,11 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const branchesOnly: string
readonly button: string export declare const button: string
readonly prefix: string export declare const input: string
readonly main: string export declare const listContainer: string
readonly input: string export declare const main: string
readonly tabContainer: string export declare const newBtnText: string
readonly branchesOnly: string export declare const popover: string
readonly popover: string export declare const prefix: string
readonly listContainer: string export declare const tabContainer: string
readonly newBtnText: string
}
export default styles

View File

@ -1,23 +1,20 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const commitsDropdown: string
readonly wrapper: string export declare const container: string
readonly header: string export declare const diffStatsLabel: string
readonly stickied: string export declare const disabled: string
readonly diffStatsLabel: string export declare const enableDiffLineBreaks: string
readonly main: string export declare const header: string
readonly enableDiffLineBreaks: string export declare const hide: string
readonly container: string export declare const hideBtn: string
readonly hideBtn: string export declare const main: string
readonly refreshIcon: string export declare const menuItem: string
readonly repeatBtn: string export declare const menuReviewItem: string
readonly popover: string export declare const popover: string
readonly menuItem: string export declare const refreshIcon: string
readonly menuReviewItem: string export declare const repeatBtn: string
readonly reviewIcon: string export declare const reviewButton: string
readonly reviewButton: string export declare const reviewIcon: string
readonly hide: string export declare const stickied: string
readonly disabled: string export declare const wrapper: string
readonly commitsDropdown: string
}
export default styles

View File

@ -1,9 +1,6 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const filesMenu: string
readonly link: string export declare const link: string
readonly filesMenu: string export declare const menuItem: string
readonly menuItem: string export declare const popover: string
readonly popover: string
}
export default styles

View File

@ -1,10 +1,7 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const cloneCopyButton: string
readonly container: string export declare const container: string
readonly label: string export declare const label: string
readonly layout: string export declare const layout: string
readonly url: string export declare const url: string
readonly cloneCopyButton: string
}
export default styles

View File

@ -1,8 +1,5 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const cloneCopyButton: string
readonly layout: string export declare const layout: string
readonly url: string export declare const url: string
readonly cloneCopyButton: string
}
export default styles

View File

@ -1,6 +1,3 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const select: string
readonly select: string
}
export default styles

View File

@ -1,20 +1,17 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const box: string
readonly main: string export declare const bp3Icon: string
readonly fluid: string export declare const deleted: string
readonly box: string export declare const deleteIcon: string
readonly viewer: string export declare const editCommentContainer: string
readonly deleted: string export declare const fluid: string
readonly outdated: string export declare const hasThread: string
readonly replyPlaceHolder: string export declare const main: string
readonly newCommentContainer: string export declare const newCommentContainer: string
readonly hasThread: string export declare const optionMenuIcon: string
readonly editCommentContainer: string export declare const outdated: string
readonly outletTopOfFirstOfComment: string export declare const outletTopOfFirstOfComment: string
readonly standalone: string export declare const replyPlaceHolder: string
readonly optionMenuIcon: string export declare const standalone: string
readonly bp3Icon: string export declare const viewer: string
readonly deleteIcon: string
}
export default styles

View File

@ -1,9 +1,6 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const commitCopyButton: string
readonly container: string export declare const container: string
readonly layout: string export declare const layout: string
readonly noCopy: string export declare const noCopy: string
readonly commitCopyButton: string
}
export default styles

View File

@ -1,10 +1,7 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const ahead: string
readonly container: string export declare const behind: string
readonly main: string export declare const container: string
readonly ahead: string export declare const main: string
readonly behind: string export declare const pipe: string
readonly pipe: string
}
export default styles

View File

@ -1,11 +1,8 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const alignContent: string
readonly commitInfoContainer: string export declare const commitInfoContainer: string
readonly commitTitleContainer: string export declare const commitTitleContainer: string
readonly infoContainer: string export declare const infoContainer: string
readonly alignContent: string export declare const infoText: string
readonly infoText: string export declare const titleText: string
readonly titleText: string
}
export default styles

View File

@ -1,11 +1,8 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const directly: string
readonly main: string export declare const extendedDescription: string
readonly extendedDescription: string export declare const main: string
readonly radioGroup: string export declare const newBranch: string
readonly directly: string export declare const newBranchContainer: string
readonly newBranch: string export declare const radioGroup: string
readonly newBranchContainer: string
}
export default styles

View File

@ -1,16 +1,13 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const commitFileButton: string
readonly container: string export declare const commitRepoButton: string
readonly table: string export declare const container: string
readonly row: string export declare const fileButton: string
readonly rowText: string export declare const label: string
readonly label: string export declare const layout: string
readonly refreshIcon: string export declare const noCopy: string
readonly fileButton: string export declare const refreshIcon: string
readonly layout: string export declare const row: string
readonly noCopy: string export declare const rowText: string
readonly commitFileButton: string export declare const table: string
readonly commitRepoButton: string
}
export default styles

View File

@ -1,10 +1,7 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const container: string
readonly container: string export declare const header: string
readonly log: string export declare const headerLayout: string
readonly header: string export declare const log: string
readonly headerLayout: string export declare const steps: string
readonly steps: string
}
export default styles

View File

@ -1,8 +1,5 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const lineNumber: string
readonly logLayout: string export declare const log: string
readonly lineNumber: string export declare const logLayout: string
readonly log: string
}
export default styles

View File

@ -1,8 +1,5 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const loading: string
readonly spin: string export declare const spin: string
readonly loading: string export declare const stepLayout: string
readonly stepLayout: string
}
export default styles

View File

@ -58,6 +58,7 @@ const ConsoleStep: FC<ConsoleStepProps> = ({ step, stageNumber, repoPath, pipeli
} }
} }
return () => { return () => {
setStreamingLogs([])
if (eventSourceRef.current) eventSourceRef.current.close() if (eventSourceRef.current) eventSourceRef.current.close()
} }
}, [executionNumber, pipelineName, repoPath, stageNumber, step?.number, step?.status]) }, [executionNumber, pipelineName, repoPath, stageNumber, step?.number, step?.status])
@ -74,7 +75,9 @@ const ConsoleStep: FC<ConsoleStepProps> = ({ step, stageNumber, repoPath, pipeli
} }
let content let content
if (loading) { if (!isOpened) {
content = null
} else if (loading) {
content = <div className={css.loading}>{getString('loading')}</div> content = <div className={css.loading}>{getString('loading')}</div>
} else if (error && step?.status !== ExecutionState.RUNNING) { } else if (error && step?.status !== ExecutionState.RUNNING) {
content = <div>Error: {error.message}</div> content = <div>Error: {error.message}</div>

View File

@ -1,13 +1,10 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const branchTagSelect: string
readonly main: string export declare const container: string
readonly title: string export declare const label: string
readonly label: string export declare const main: string
readonly container: string export declare const maxContainer: string
readonly maxContainer: string export declare const popoverContainer: string
readonly branchTagSelect: string export declare const selectContainer: string
readonly selectContainer: string export declare const title: string
readonly popoverContainer: string
}
export default styles

View File

@ -1,13 +1,10 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const branchTagSelect: string
readonly main: string export declare const container: string
readonly title: string export declare const extendedDescription: string
readonly label: string export declare const label: string
readonly container: string export declare const main: string
readonly extendedDescription: string export declare const popoverContainer: string
readonly branchTagSelect: string export declare const selectContainer: string
readonly selectContainer: string export declare const title: string
readonly popoverContainer: string
}
export default styles

View File

@ -1,13 +1,10 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const collapsed: string
readonly main: string export declare const diffContent: string
readonly readOnly: string export declare const diffHeader: string
readonly collapsed: string export declare const fname: string
readonly diffHeader: string export declare const main: string
readonly diffContent: string export declare const readOnly: string
readonly fname: string export declare const standalone: string
readonly viewLabel: string export declare const viewLabel: string
readonly standalone: string
}
export default styles

View File

@ -143,7 +143,7 @@ export function getCommentLineInfo(
) { ) {
const isSideBySideView = viewStyle === ViewStyle.SIDE_BY_SIDE const isSideBySideView = viewStyle === ViewStyle.SIDE_BY_SIDE
const { left, lineNumber, filePath } = commentEntry const { left, lineNumber, filePath } = commentEntry
const filePathBody = contentDOM?.querySelector(`[data="${filePath}"`) const filePathBody = filePath ? contentDOM?.querySelector(`[data="${filePath}"`) : contentDOM
const diffBody = filePathBody?.querySelector( const diffBody = filePathBody?.querySelector(
`${isSideBySideView ? `.d2h-file-side-diff${left ? '.left' : '.right'} ` : ''}.d2h-diff-tbody` `${isSideBySideView ? `.d2h-file-side-diff${left ? '.left' : '.right'} ` : ''}.d2h-diff-tbody`

View File

@ -1,6 +1,3 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const editor: string
readonly editor: string
}
export default styles

View File

@ -1,11 +1,7 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const breadcrumb: string
readonly pageHeader: string export declare const executionInfo: string
readonly header: string export declare const hash: string
readonly breadcrumb: string export declare const pageHeader: string
readonly hash: string export declare const timer: string
readonly timer: string
readonly executionInfo: string
}
export default styles

View File

@ -1,12 +1,9 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const container: string
readonly container: string export declare const layout: string
readonly menu: string export declare const menu: string
readonly menuItem: string export declare const menuItem: string
readonly layout: string export declare const selected: string
readonly selected: string export declare const statusIcon: string
readonly uid: string export declare const uid: string
readonly statusIcon: string
}
export default styles

View File

@ -1,13 +1,10 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const error: string
readonly main: string export declare const failure: string
readonly iconOnly: string export declare const iconOnly: string
readonly noBackground: string export declare const main: string
readonly pending: string export declare const noBackground: string
readonly running: string export declare const pending: string
readonly success: string export declare const running: string
readonly failure: string export declare const success: string
readonly error: string
}
export default styles

View File

@ -1,10 +1,7 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const executionStatus: string
readonly executionStatus: string export declare const failure: string
readonly iconOnly: string export declare const iconOnly: string
readonly success: string export declare const open: string
readonly failure: string export declare const success: string
readonly open: string
}
export default styles

View File

@ -1,8 +1,5 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const copyContainer: string
readonly link: string export declare const link: string
readonly copyContainer: string export declare const linkText: string
readonly linkText: string
}
export default styles

View File

@ -1,8 +1,5 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const layout: string
readonly main: string export declare const main: string
readonly layout: string export declare const text: string
readonly text: string
}
export default styles

View File

@ -1,10 +1,7 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const content: string
readonly imageModal: string export declare const image: string
readonly content: string export declare const imageModal: string
readonly image: string export declare const portalContainer: string
readonly vertical: string export declare const vertical: string
readonly portalContainer: string
}
export default styles

View File

@ -1,11 +1,8 @@
/* eslint-disable */ /* eslint-disable */
// this is an auto-generated file // This is an auto-generated file
declare const styles: { export declare const commitLink: string
readonly latestCommit: string export declare const forFile: string
readonly standalone: string export declare const latestCommit: string
readonly forFile: string export declare const shaBtn: string
readonly shaBtn: string export declare const standalone: string
readonly commitLink: string export declare const time: string
readonly time: string
}
export default styles

Some files were not shown because too many files have changed in this diff Show More