Files
portainer/api/http/handler/backup/restore_from_s3.go
T
Dmitry Salakhov 37baabe134 EE-292: backup to and restore from s3 (#240)
* EE-384: add endpoint to set auto backup (#224)

* EE-383: add endpoint to fetch backup settings (#231)

* add get backup settings handler
* add api docs desc

* EE-382: restore from s3 (#233)

* EE-381: add GET backup status handler (#234)

* EE-385: Add S3 backup execute handler (#237)

* add s3 backup execute handler

* refactories inside `./api/backup/backup_scheduler.go` and `./api/backup/backup_scheduler.go`

* fix tests

* EE-375: added backup to S3 form

* EE-376: added restore from S3 form

* EE-377: Update Home screen to display last backup run status

* update backup service with back end endpoints.

* restart admin monitor during s3 restores

* use go 1.13

* go 1.13 compatibility

* EE-375: added cron-validator lib

* EE-375: using enum to compare form types

* EE-375: validate cron rule field

* try fix windows build

* EE-375 EE-376 backup and restore forms validation changes

* fix(autobackup): update autobackup settings validation rules (#260)

* fix(autobackup): automate backup to s3 fe update (#261)

* EE-292: fixed typo in property.

* EE-292: updated auto backup front end validation.

* EE-292: updated lib to validate cron rule in front end

* fix dependencies

* bumped libcompose version

Co-authored-by: Hui <arris_li@hotmail.com>
Co-authored-by: Felix Han <felix.han@portainer.io>
Co-authored-by: fhanportainer <79428273+fhanportainer@users.noreply.github.com>
2021-04-15 12:12:53 +12:00

117 lines
3.9 KiB
Go

package backup
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"time"
"github.com/pkg/errors"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
portainer "github.com/portainer/portainer/api"
operations "github.com/portainer/portainer/api/backup"
s3client "github.com/portainer/portainer/api/s3"
)
type restoreS3Settings struct {
portainer.S3Location
Password string
}
func (p *restoreS3Settings) Validate(r *http.Request) error {
if p.AccessKeyID == "" {
return errors.New("missing AccessKeyID field")
}
if p.SecretAccessKey == "" {
return errors.New("missing SecretAccessKe field")
}
if p.Region == "" {
return errors.New("missing Region field")
}
if p.BucketName == "" {
return errors.New("missing BucketName field")
}
if p.Filename == "" {
return errors.New("missing Filename field")
}
return nil
}
// @id RestoreFromS3
// @summary Triggers a system restore using details of s3 backup
// @description Triggers a system restore using details of s3 backup
// @description **Access policy**: public
// @tags backup
// @param AccessKeyID body string false "AWS access key id"
// @param SecretAccessKey body string false "AWS secret access key"
// @param Region body string false "AWS S3 region"
// @param BucketName body string false "AWS S3 bucket name"
// @param Filename body string false "AWS S3 filename in the bucket"
// @param Password body string false "Password to decrypt the backup with"
// @success 200 "Success"
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /backup/s3/restore [post]
func (h *Handler) restoreFromS3(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
initialized, err := h.adminMonitor.WasInitialized()
if err != nil {
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Failed to check system initialization", Err: err}
}
if initialized {
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Cannot restore already initialized instance", Err: fmt.Errorf("system already initialized")}
}
h.adminMonitor.Stop()
defer h.adminMonitor.Start()
var payload restoreS3Settings
err = request.DecodeAndValidateJSONPayload(r, &payload)
if err != nil {
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err}
}
backupFile, err := createTmpBackupLocation(h.filestorePath)
if err != nil {
log.Println(err)
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Failed to restore", Err: err}
}
defer func() {
backupFile.Close()
os.RemoveAll(filepath.Dir(backupFile.Name()))
}()
s3session, err := s3client.NewSession(payload.Region, payload.AccessKeyID, payload.SecretAccessKey)
if err != nil {
log.Printf("[ERROR] %s \n", err)
}
if err = s3client.Download(s3session, backupFile, payload.S3Location); err != nil {
log.Printf("[ERROR] %s \n", errors.Wrap(err, "failed downloading file from S3"))
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Failed to download file from S3", Err: err}
}
if err = operations.RestoreArchive(backupFile, payload.Password, h.filestorePath, h.gate, h.dataStore, h.shutdownTrigger); err != nil {
log.Printf("[ERROR] %s", errors.Wrap(err, "faild to restore system from backup"))
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Failed to restore backup", Err: err}
}
return nil
}
func createTmpBackupLocation(filestorePath string) (*os.File, error) {
restoreDir, err := ioutil.TempDir(filestorePath, fmt.Sprintf("restore_%s", time.Now().Format("2006-01-02_15-04-05")))
if err != nil {
return nil, errors.New("failed to create tmp download dir")
}
f, err := os.Create(filepath.Join(restoreDir, "backup_file"))
if err != nil {
return nil, errors.New("failed to create tmp download file")
}
return f, nil
}