Add restorations service and related functions

This commit is contained in:
Luis Eduardo Jeréz Girón
2024-08-04 19:20:44 -06:00
parent 50050cc895
commit 86a3d70455
11 changed files with 332 additions and 0 deletions
@@ -0,0 +1,13 @@
package restorations
import (
"context"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
)
func (s *Service) CreateRestoration(
ctx context.Context, params dbgen.RestorationsServiceCreateRestorationParams,
) (dbgen.Restoration, error) {
return s.dbgen.RestorationsServiceCreateRestoration(ctx, params)
}
@@ -0,0 +1,4 @@
-- name: RestorationsServiceCreateRestoration :one
INSERT INTO restorations (execution_id, database_id, status, message)
VALUES (@execution_id, @database_id, @status, @message)
RETURNING *;
@@ -0,0 +1,7 @@
package restorations
import "context"
func (s *Service) GetRestorationsQty(ctx context.Context) (int64, error) {
return s.dbgen.RestorationsServiceGetRestorationsQty(ctx)
}
@@ -0,0 +1,2 @@
-- name: RestorationsServiceGetRestorationsQty :one
SELECT COUNT(*) FROM restorations;
@@ -0,0 +1,54 @@
package restorations
import (
"context"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/util/paginateutil"
"github.com/google/uuid"
)
type PaginateRestorationsParams struct {
Page int
Limit int
ExecutionFilter uuid.NullUUID
DatabaseFilter uuid.NullUUID
}
func (s *Service) PaginateRestorations(
ctx context.Context, params PaginateRestorationsParams,
) (paginateutil.PaginateResponse, []dbgen.RestorationsServicePaginateRestorationsRow, error) {
page := max(params.Page, 1)
limit := min(max(params.Limit, 1), 100)
count, err := s.dbgen.RestorationsServicePaginateRestorationsCount(
ctx, dbgen.RestorationsServicePaginateRestorationsCountParams{
ExecutionID: params.ExecutionFilter,
DatabaseID: params.DatabaseFilter,
},
)
if err != nil {
return paginateutil.PaginateResponse{}, nil, err
}
paginateParams := paginateutil.PaginateParams{
Page: page,
Limit: limit,
}
offset := paginateutil.CreateOffsetFromParams(paginateParams)
paginateResponse := paginateutil.CreatePaginateResponse(paginateParams, int(count))
restorations, err := s.dbgen.RestorationsServicePaginateRestorations(
ctx, dbgen.RestorationsServicePaginateRestorationsParams{
ExecutionID: params.ExecutionFilter,
DatabaseID: params.DatabaseFilter,
Limit: int32(params.Limit),
Offset: int32(offset),
},
)
if err != nil {
return paginateutil.PaginateResponse{}, nil, err
}
return paginateResponse, restorations, nil
}
@@ -0,0 +1,39 @@
-- name: RestorationsServicePaginateRestorationsCount :one
SELECT COUNT(restorations.*)
FROM restorations
INNER JOIN executions ON executions.id = restorations.execution_id
LEFT JOIN databases ON databases.id = restorations.database_id
WHERE
(
sqlc.narg('execution_id')::UUID IS NULL
OR
restorations.execution_id = sqlc.narg('execution_id')::UUID
)
AND
(
sqlc.narg('database_id')::UUID IS NULL
OR
restorations.database_id = sqlc.narg('database_id')::UUID
);
-- name: RestorationsServicePaginateRestorations :many
SELECT
restorations.*,
databases.name AS database_name
FROM restorations
INNER JOIN executions ON executions.id = restorations.execution_id
LEFT JOIN databases ON databases.id = restorations.database_id
WHERE
(
sqlc.narg('execution_id')::UUID IS NULL
OR
restorations.execution_id = sqlc.narg('execution_id')::UUID
)
AND
(
sqlc.narg('database_id')::UUID IS NULL
OR
restorations.database_id = sqlc.narg('database_id')::UUID
)
ORDER BY restorations.started_at DESC
LIMIT sqlc.arg('limit') OFFSET sqlc.arg('offset');
@@ -0,0 +1,31 @@
package restorations
import (
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/integration"
"github.com/eduardolat/pgbackweb/internal/service/databases"
"github.com/eduardolat/pgbackweb/internal/service/destinations"
"github.com/eduardolat/pgbackweb/internal/service/executions"
)
type Service struct {
dbgen *dbgen.Queries
ints *integration.Integration
executionsService *executions.Service
databasesService *databases.Service
destinationsService *destinations.Service
}
func New(
dbgen *dbgen.Queries, ints *integration.Integration,
executionsService *executions.Service, databasesService *databases.Service,
destinationsService *destinations.Service,
) *Service {
return &Service{
dbgen: dbgen,
ints: ints,
executionsService: executionsService,
databasesService: databasesService,
destinationsService: destinationsService,
}
}
@@ -0,0 +1,155 @@
package restorations
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/logger"
"github.com/google/uuid"
)
// RunRestoration runs a backup restoration
func (s *Service) RunRestoration(
ctx context.Context,
executionID uuid.UUID,
databaseID uuid.NullUUID,
connString string,
) error {
updateRes := func(params dbgen.RestorationsServiceUpdateRestorationParams) error {
_, err := s.dbgen.RestorationsServiceUpdateRestoration(
ctx, params,
)
return err
}
logError := func(err error) {
dbID := "empty"
if databaseID.Valid {
dbID = databaseID.UUID.String()
}
logger.Error("error running restoration", logger.KV{
"execution_id": executionID.String(),
"database_id": dbID,
"error": err.Error(),
})
}
res, err := s.CreateRestoration(ctx, dbgen.RestorationsServiceCreateRestorationParams{
ExecutionID: executionID,
DatabaseID: databaseID,
Status: "running",
})
if err != nil {
logError(err)
return err
}
if !databaseID.Valid && connString == "" {
err := fmt.Errorf("database_id or connection_string must be provided")
logError(err)
return updateRes(dbgen.RestorationsServiceUpdateRestorationParams{
ID: res.ID,
Status: sql.NullString{Valid: true, String: "failed"},
Message: sql.NullString{Valid: true, String: err.Error()},
FinishedAt: sql.NullTime{Valid: true, Time: time.Now()},
})
}
execution, err := s.executionsService.GetExecution(ctx, executionID)
if err != nil {
logError(err)
return updateRes(dbgen.RestorationsServiceUpdateRestorationParams{
ID: res.ID,
Status: sql.NullString{Valid: true, String: "failed"},
Message: sql.NullString{Valid: true, String: err.Error()},
FinishedAt: sql.NullTime{Valid: true, Time: time.Now()},
})
}
if execution.Status != "success" || !execution.Path.Valid {
err := fmt.Errorf("backup execution must be successful")
logError(err)
return updateRes(dbgen.RestorationsServiceUpdateRestorationParams{
ID: res.ID,
Status: sql.NullString{Valid: true, String: "failed"},
Message: sql.NullString{Valid: true, String: err.Error()},
FinishedAt: sql.NullTime{Valid: true, Time: time.Now()},
})
}
if databaseID.Valid {
db, err := s.databasesService.GetDatabase(ctx, databaseID.UUID)
if err != nil {
logError(err)
return updateRes(dbgen.RestorationsServiceUpdateRestorationParams{
ID: res.ID,
Status: sql.NullString{Valid: true, String: "failed"},
Message: sql.NullString{Valid: true, String: err.Error()},
FinishedAt: sql.NullTime{Valid: true, Time: time.Now()},
})
}
connString = db.DecryptedConnectionString
}
pgVersion, err := s.ints.PGClient.ParseVersion(execution.DatabasePgVersion)
if err != nil {
logError(err)
return updateRes(dbgen.RestorationsServiceUpdateRestorationParams{
ID: res.ID,
Status: sql.NullString{Valid: true, String: "failed"},
Message: sql.NullString{Valid: true, String: err.Error()},
FinishedAt: sql.NullTime{Valid: true, Time: time.Now()},
})
}
err = s.ints.PGClient.Ping(pgVersion, connString)
if err != nil {
logError(err)
return updateRes(dbgen.RestorationsServiceUpdateRestorationParams{
ID: res.ID,
Status: sql.NullString{Valid: true, String: "failed"},
Message: sql.NullString{Valid: true, String: err.Error()},
FinishedAt: sql.NullTime{Valid: true, Time: time.Now()},
})
}
isLocal, zipURLOrPath, err := s.executionsService.GetExecutionDownloadLinkOrPath(
ctx, executionID,
)
if err != nil {
logError(err)
return updateRes(dbgen.RestorationsServiceUpdateRestorationParams{
ID: res.ID,
Status: sql.NullString{Valid: true, String: "failed"},
Message: sql.NullString{Valid: true, String: err.Error()},
FinishedAt: sql.NullTime{Valid: true, Time: time.Now()},
})
}
err = s.ints.PGClient.RestoreZip(
pgVersion, connString, isLocal, zipURLOrPath,
)
if err != nil {
logError(err)
return updateRes(dbgen.RestorationsServiceUpdateRestorationParams{
ID: res.ID,
Status: sql.NullString{Valid: true, String: "failed"},
Message: sql.NullString{Valid: true, String: err.Error()},
FinishedAt: sql.NullTime{Valid: true, Time: time.Now()},
})
}
logger.Info("backup restored successfully", logger.KV{
"restoration_id": res.ID.String(),
"execution_id": executionID.String(),
})
return updateRes(dbgen.RestorationsServiceUpdateRestorationParams{
ID: res.ID,
Status: sql.NullString{Valid: true, String: "success"},
Message: sql.NullString{Valid: true, String: "Backup restored successfully"},
FinishedAt: sql.NullTime{Valid: true, Time: time.Now()},
})
}
@@ -0,0 +1,13 @@
package restorations
import (
"context"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
)
func (s *Service) UpdateRestoration(
ctx context.Context, params dbgen.RestorationsServiceUpdateRestorationParams,
) (dbgen.Restoration, error) {
return s.dbgen.RestorationsServiceUpdateRestoration(ctx, params)
}
@@ -0,0 +1,8 @@
-- name: RestorationsServiceUpdateRestoration :one
UPDATE restorations
SET
status = COALESCE(sqlc.narg('status'), status),
message = COALESCE(sqlc.narg('message'), message),
finished_at = COALESCE(sqlc.narg('finished_at'), finished_at)
WHERE id = @id
RETURNING *;
+6
View File
@@ -10,6 +10,7 @@ import (
"github.com/eduardolat/pgbackweb/internal/service/databases"
"github.com/eduardolat/pgbackweb/internal/service/destinations"
"github.com/eduardolat/pgbackweb/internal/service/executions"
"github.com/eduardolat/pgbackweb/internal/service/restorations"
"github.com/eduardolat/pgbackweb/internal/service/users"
)
@@ -20,6 +21,7 @@ type Service struct {
DestinationsService *destinations.Service
ExecutionsService *executions.Service
UsersService *users.Service
RestorationsService *restorations.Service
}
func New(
@@ -32,6 +34,9 @@ func New(
executionsService := executions.New(env, dbgen, ints)
usersService := users.New(dbgen)
backupsService := backups.New(dbgen, cr, executionsService)
restorationsService := restorations.New(
dbgen, ints, executionsService, databasesService, destinationsService,
)
return &Service{
AuthService: authService,
@@ -40,5 +45,6 @@ func New(
DestinationsService: destinationsService,
ExecutionsService: executionsService,
UsersService: usersService,
RestorationsService: restorationsService,
}
}