Restore the space to its exact state at the time of deletion (#1180)

gitness-test1
Atefeh Mohseni-Ejiyeh 2024-04-08 18:19:07 +00:00 committed by Harness
parent ad5989dae4
commit aa330928dd
13 changed files with 123 additions and 81 deletions

View File

@ -64,6 +64,14 @@ func (c *Controller) PurgeNoAuth(
session *auth.Session,
repo *types.Repository,
) error {
if repo.Importing {
log.Ctx(ctx).Info().Msg("repository is importing. cancelling the import job.")
err := c.importer.Cancel(ctx, repo)
if err != nil {
return fmt.Errorf("failed to cancel repository import")
}
}
if err := c.repoStore.Purge(ctx, repo.ID, repo.Deleted); err != nil {
return fmt.Errorf("failed to delete repo from db: %w", err)
}
@ -86,12 +94,6 @@ func (c *Controller) DeleteGitRepository(
session *auth.Session,
repo *types.Repository,
) error {
if repo.Importing {
log.Ctx(ctx).Debug().Str("repo.git_uid", repo.GitUID).
Msg("skipping removal of git directory for repository being imported")
return nil
}
writeParams, err := controller.CreateRPCInternalWriteParams(ctx, c.urlProvider, session, repo)
if err != nil {
return fmt.Errorf("failed to create RPC write params: %w", err)

View File

@ -67,22 +67,22 @@ func (c *Controller) Restore(
parentID = space.ID
}
return c.RestoreNoAuth(ctx, repo, in.NewIdentifier, &parentID)
return c.RestoreNoAuth(ctx, repo, in.NewIdentifier, parentID)
}
func (c *Controller) RestoreNoAuth(
ctx context.Context,
repo *types.Repository,
newIdentifier *string,
newParentID *int64,
newParentID int64,
) (*types.Repository, error) {
var err error
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
if err := c.resourceLimiter.RepoCount(ctx, *newParentID, 1); err != nil {
if err := c.resourceLimiter.RepoCount(ctx, newParentID, 1); err != nil {
return fmt.Errorf("resource limit exceeded: %w", limiter.ErrMaxNumReposReached)
}
repo, err = c.repoStore.Restore(ctx, repo, newIdentifier, newParentID)
repo, err = c.repoStore.Restore(ctx, repo, newIdentifier, &newParentID)
if err != nil {
return fmt.Errorf("failed to restore the repo: %w", err)
}

View File

@ -48,26 +48,17 @@ func (c *Controller) SoftDelete(
return nil, fmt.Errorf("access check failed: %w", err)
}
if repo.Deleted != nil {
return nil, usererror.BadRequest("repository has been already deleted")
}
log.Ctx(ctx).Info().
Int64("repo.id", repo.ID).
Str("repo.path", repo.Path).
Msg("soft deleting repository")
if repo.Deleted != nil {
return nil, usererror.BadRequest("repository has been already deleted")
}
if repo.Importing {
log.Ctx(ctx).Info().Msg("repository is importing. cancelling the import job and purge the repo.")
err = c.importer.Cancel(ctx, repo)
if err != nil {
return nil, fmt.Errorf("failed to cancel repository import")
}
return nil, c.PurgeNoAuth(ctx, session, repo)
}
now := time.Now().UnixMilli()
if err = c.SoftDeleteNoAuth(ctx, repo, now); err != nil {
if err = c.SoftDeleteNoAuth(ctx, session, repo, now); err != nil {
return nil, fmt.Errorf("failed to soft delete repo: %w", err)
}
@ -76,9 +67,14 @@ func (c *Controller) SoftDelete(
func (c *Controller) SoftDeleteNoAuth(
ctx context.Context,
session *auth.Session,
repo *types.Repository,
deletedAt int64,
) error {
if repo.Importing {
return c.PurgeNoAuth(ctx, session, repo)
}
err := c.repoStore.SoftDelete(ctx, repo, deletedAt)
if err != nil {
return fmt.Errorf("failed to soft delete repo from db: %w", err)

View File

@ -109,31 +109,41 @@ func (c *Controller) restoreSpaceInnerInTx(
newParentID *int64,
spacePath string,
) (*types.Space, error) {
// restore the target space
restoredSpace, err := c.restoreNoAuth(ctx, space, newIdentifier, newParentID)
if err != nil {
return nil, fmt.Errorf("failed to restore space: %w", err)
}
if err = check.PathDepth(restoredSpace.Path, true); err != nil {
return nil, fmt.Errorf("path is invalid: %w", err)
}
repoCount, err := c.repoStore.Count(
ctx,
space.ID,
&types.RepoFilter{DeletedBeforeOrAt: &deletedAt, Recursive: true},
&types.RepoFilter{DeletedAt: &deletedAt, Recursive: true},
)
if err != nil {
return nil, fmt.Errorf("failed to count repos in space %d recursively: %w", space.ID, err)
return nil, fmt.Errorf("failed to count repos in space recursively: %w", err)
}
if err := c.resourceLimiter.RepoCount(ctx, *newParentID, int(repoCount)); err != nil {
if err := c.resourceLimiter.RepoCount(ctx, space.ID, int(repoCount)); err != nil {
return nil, fmt.Errorf("resource limit exceeded: %w", limiter.ErrMaxNumReposReached)
}
filter := &types.SpaceFilter{
Page: 1,
Size: math.MaxInt,
Query: "",
Order: enum.OrderDesc,
Sort: enum.SpaceAttrCreated,
DeletedBeforeOrAt: &deletedAt,
Recursive: true,
Page: 1,
Size: math.MaxInt,
Query: "",
Order: enum.OrderDesc,
Sort: enum.SpaceAttrCreated,
DeletedAt: &deletedAt,
Recursive: true,
}
subSpaces, err := c.spaceStore.List(ctx, space.ID, filter)
if err != nil {
return nil, fmt.Errorf("failed to list space %d sub spaces recursively: %w", space.ID, err)
return nil, fmt.Errorf("failed to list space sub spaces recursively: %w", err)
}
var subspacePath string
@ -153,17 +163,7 @@ func (c *Controller) restoreSpaceInnerInTx(
}
if err := c.restoreRepositoriesNoAuth(ctx, space.ID, deletedAt); err != nil {
return nil, fmt.Errorf("failed to restore space %d repositories: %w", space.ID, err)
}
// restore the target space
restoredSpace, err := c.restoreNoAuth(ctx, space, newIdentifier, newParentID)
if err != nil {
return nil, fmt.Errorf("failed to restore space: %w", err)
}
if err = check.PathDepth(restoredSpace.Path, true); err != nil {
return nil, fmt.Errorf("path is invalid: %w", err)
return nil, fmt.Errorf("failed to restore space repositories: %w", err)
}
return restoredSpace, nil
@ -208,13 +208,13 @@ func (c *Controller) restoreRepositoriesNoAuth(
deletedAt int64,
) error {
filter := &types.RepoFilter{
Page: 1,
Size: int(math.MaxInt),
Query: "",
Order: enum.OrderAsc,
Sort: enum.RepoAttrNone,
DeletedBeforeOrAt: &deletedAt,
Recursive: true,
Page: 1,
Size: int(math.MaxInt),
Query: "",
Order: enum.OrderAsc,
Sort: enum.RepoAttrNone,
DeletedAt: &deletedAt,
Recursive: true,
}
repos, err := c.repoStore.List(ctx, spaceID, filter)
if err != nil {
@ -222,7 +222,7 @@ func (c *Controller) restoreRepositoriesNoAuth(
}
for _, repo := range repos {
_, err = c.repoCtrl.RestoreNoAuth(ctx, repo, nil, nil)
_, err = c.repoCtrl.RestoreNoAuth(ctx, repo, nil, repo.ParentID)
if err != nil {
return fmt.Errorf("failed to restore repository: %w", err)
}

View File

@ -52,20 +52,21 @@ func (c *Controller) SoftDelete(
return nil, fmt.Errorf("failed to check access: %w", err)
}
return c.SoftDeleteNoAuth(ctx, space)
return c.SoftDeleteNoAuth(ctx, session, space)
}
// SoftDeleteNoAuth soft deletes the space - no authorization is verified.
// WARNING For internal calls only.
func (c *Controller) SoftDeleteNoAuth(
ctx context.Context,
session *auth.Session,
space *types.Space,
) (*SoftDeleteResponse, error) {
var softDelRes *SoftDeleteResponse
var err error
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
softDelRes, err = c.softDeleteInnerInTx(ctx, space)
softDelRes, err = c.softDeleteInnerInTx(ctx, session, space)
return err
})
if err != nil {
@ -77,6 +78,7 @@ func (c *Controller) SoftDeleteNoAuth(
func (c *Controller) softDeleteInnerInTx(
ctx context.Context,
session *auth.Session,
space *types.Space,
) (*SoftDeleteResponse, error) {
filter := &types.SpaceFilter{
@ -101,7 +103,7 @@ func (c *Controller) softDeleteInnerInTx(
}
}
err = c.softDeleteRepositoriesNoAuth(ctx, space.ID, now)
err = c.softDeleteRepositoriesNoAuth(ctx, session, space.ID, now)
if err != nil {
return nil, fmt.Errorf("failed to soft delete repositories of space %d: %w", space.ID, err)
}
@ -122,6 +124,7 @@ func (c *Controller) softDeleteInnerInTx(
// WARNING For internal calls only.
func (c *Controller) softDeleteRepositoriesNoAuth(
ctx context.Context,
session *auth.Session,
spaceID int64,
deletedAt int64,
) error {
@ -140,7 +143,7 @@ func (c *Controller) softDeleteRepositoriesNoAuth(
}
for _, repo := range repos {
err = c.repoCtrl.SoftDeleteNoAuth(ctx, repo, deletedAt)
err = c.repoCtrl.SoftDeleteNoAuth(ctx, session, repo, deletedAt)
if err != nil {
return fmt.Errorf("failed to soft delete repository: %w", err)
}

View File

@ -154,18 +154,15 @@ func ParseRecursiveFromQuery(r *http.Request) (bool, error) {
// GetDeletedAtFromQueryOrError gets the exact resource deletion timestamp from the query.
func GetDeletedAtFromQueryOrError(r *http.Request) (int64, error) {
return QueryParamAsPositiveInt64(r, QueryParamDeletedAt)
return QueryParamAsPositiveInt64OrError(r, QueryParamDeletedAt)
}
// GetDeletedBeforeOrAtFromQuery gets the resource deletion timestamp from the query as an optional parameter.
func GetDeletedBeforeOrAtFromQuery(r *http.Request) (int64, bool, error) {
value, err := QueryParamAsPositiveInt64OrDefault(r, QueryParamDeletedBeforeOrAt, 0)
if err != nil {
return 0, false, err
}
if value == 0 {
return value, false, nil
}
return value, true, nil
return QueryParamAsPositiveInt64(r, QueryParamDeletedBeforeOrAt)
}
// GetDeletedAtFromQuery gets the exact resource deletion timestamp from the query as an optional parameter.
func GetDeletedAtFromQuery(r *http.Request) (int64, bool, error) {
return QueryParamAsPositiveInt64(r, QueryParamDeletedAt)
}

View File

@ -53,13 +53,23 @@ func ParseRepoFilter(r *http.Request) (*types.RepoFilter, error) {
}
// deletedBeforeOrAt is optional to retrieve repos deleted before or at the specified timestamp.
var deletionTime *int64
value, ok, err := GetDeletedBeforeOrAtFromQuery(r)
var deletedBeforeOrAt *int64
deletionVal, ok, err := GetDeletedBeforeOrAtFromQuery(r)
if err != nil {
return nil, err
}
if ok {
deletionTime = &value
deletedBeforeOrAt = &deletionVal
}
// deletedAt is optional to retrieve repos deleted at the specified timestamp.
var deletedAt *int64
deletedAtVal, ok, err := GetDeletedAtFromQuery(r)
if err != nil {
return nil, err
}
if ok {
deletedAt = &deletedAtVal
}
return &types.RepoFilter{
@ -69,6 +79,7 @@ func ParseRepoFilter(r *http.Request) (*types.RepoFilter, error) {
Sort: ParseSortRepo(r),
Size: ParseLimit(r),
Recursive: recursive,
DeletedBeforeOrAt: deletionTime,
DeletedAt: deletedAt,
DeletedBeforeOrAt: deletedBeforeOrAt,
}, nil
}

View File

@ -52,13 +52,23 @@ func ParseSpaceFilter(r *http.Request) (*types.SpaceFilter, error) {
}
// deletedBeforeOrAt is optional to retrieve spaces deleted before or at the specified timestamp.
var deletionTime *int64
value, ok, err := GetDeletedBeforeOrAtFromQuery(r)
var deletedBeforeOrAt *int64
deletionVal, ok, err := GetDeletedBeforeOrAtFromQuery(r)
if err != nil {
return nil, err
}
if ok {
deletionTime = &value
deletedBeforeOrAt = &deletionVal
}
// deletedAt is optional to retrieve spaces deleted at the specified timestamp.
var deletedAt *int64
deletedAtVal, ok, err := GetDeletedAtFromQuery(r)
if err != nil {
return nil, err
}
if ok {
deletedAt = &deletedAtVal
}
return &types.SpaceFilter{
@ -68,6 +78,7 @@ func ParseSpaceFilter(r *http.Request) (*types.SpaceFilter, error) {
Sort: ParseSortSpace(r),
Size: ParseLimit(r),
Recursive: recursive,
DeletedBeforeOrAt: deletionTime,
DeletedAt: deletedAt,
DeletedBeforeOrAt: deletedBeforeOrAt,
}, nil
}

View File

@ -150,9 +150,9 @@ func QueryParamAsPositiveInt64OrDefault(r *http.Request, paramName string, deflt
return valueInt, nil
}
// QueryParamAsPositiveInt64 extracts an integer parameter from the request query.
// QueryParamAsPositiveInt64OrError extracts an integer parameter from the request query.
// If the parameter doesn't exist an error is returned.
func QueryParamAsPositiveInt64(r *http.Request, paramName string) (int64, error) {
func QueryParamAsPositiveInt64OrError(r *http.Request, paramName string) (int64, error) {
value, err := QueryParamOrError(r, paramName)
if err != nil {
return 0, err
@ -166,6 +166,21 @@ func QueryParamAsPositiveInt64(r *http.Request, paramName string) (int64, error)
return valueInt, nil
}
// QueryParamAsPositiveInt64 extracts an integer parameter from the request query if it exists.
func QueryParamAsPositiveInt64(r *http.Request, paramName string) (int64, bool, error) {
value, ok := QueryParam(r, paramName)
if !ok {
return 0, false, nil
}
valueInt, err := strconv.ParseInt(value, 10, 64)
if err != nil || valueInt <= 0 {
return 0, false, usererror.BadRequestf("Parameter '%s' must be a positive integer.", paramName)
}
return valueInt, true, nil
}
// PathParamAsPositiveInt64 extracts an integer parameter from the request path.
func PathParamAsPositiveInt64(r *http.Request, paramName string) (int64, error) {
rawValue, err := PathParamOrError(r, paramName)

View File

@ -843,7 +843,10 @@ func applyQueryFilter(stmt squirrel.SelectBuilder, filter *types.RepoFilter) squ
if filter.Query != "" {
stmt = stmt.Where("LOWER(repo_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query)))
}
if filter.DeletedBeforeOrAt != nil {
//nolint:gocritic
if filter.DeletedAt != nil {
stmt = stmt.Where("repo_deleted = ?", filter.DeletedAt)
} else if filter.DeletedBeforeOrAt != nil {
stmt = stmt.Where("repo_deleted <= ?", filter.DeletedBeforeOrAt)
} else {
stmt = stmt.Where("repo_deleted IS NULL")

View File

@ -620,8 +620,10 @@ func (s *SpaceStore) applyQueryFilter(
if opts.Query != "" {
stmt = stmt.Where("LOWER(space_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(opts.Query)))
}
if opts.DeletedBeforeOrAt != nil {
//nolint:gocritic
if opts.DeletedAt != nil {
stmt = stmt.Where("space_deleted = ?", opts.DeletedAt)
} else if opts.DeletedBeforeOrAt != nil {
stmt = stmt.Where("space_deleted <= ?", opts.DeletedBeforeOrAt)
} else {
stmt = stmt.Where("space_deleted IS NULL")

View File

@ -87,6 +87,7 @@ type RepoFilter struct {
Query string `json:"query"`
Sort enum.RepoAttr `json:"sort"`
Order enum.Order `json:"order"`
DeletedAt *int64 `json:"deleted_at,omitempty"`
DeletedBeforeOrAt *int64 `json:"deleted_before_or_at,omitempty"`
Recursive bool
}

View File

@ -66,6 +66,7 @@ type SpaceFilter struct {
Query string `json:"query"`
Sort enum.SpaceAttr `json:"sort"`
Order enum.Order `json:"order"`
DeletedAt *int64 `json:"deleted_at,omitempty"`
DeletedBeforeOrAt *int64 `json:"deleted_before_or_at,omitempty"`
Recursive bool `json:"recursive"`
}