mirror of https://github.com/harness/drone.git
Restore the space to its exact state at the time of deletion (#1180)
parent
ad5989dae4
commit
aa330928dd
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue