Files
portainer/api/http/handler/backup/restore_test.go
Dmitry Salakhov e15b908983 Feat(backup): add the ability to backup and restore portainer from file [EE-279] (#204)
* EE-319: backup endpoint (#193)

* feat(backup):
* add an orbiter to block writes while backup
* add backup handler
* add an ability to tar.gz a dir
* add aes encryption support

* EE-320: restore endpoint (#196)

* feat(backup):
* add restore handler
* re-init system state after restore

* feat(backup): Update server to respect readonly lock (#199)

* feat(backup): EE-322 Add backup and restore screen (#198)

Co-authored-by: Simon Meng <simon.meng@portainer.io>

* name archive as portainer-backup_yyyy-mm-dd_hh-mm-ss

* backup custom templates and edge jobs

* restart http and proxy servers after restore to re-init internal state

* feat(backup): EE-322 hide password field if password protect toggle is off

* feat(backup): EE-322 add tooltip for password field of restore backup

* feat(backup): EE-322 wait for backend restart after restoring

* Shutdown background go-routines

* changed restore err message when cannot extract

* fix: symlinks are ignored from backups

* replace single admin check with a restartable monitor (#238)

* clean log

Co-authored-by: Maxime Bajeux <max.bajeux@gmail.com>
Co-authored-by: cong meng <mcpacino@gmail.com>
Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-04-06 15:41:41 +12:00

124 lines
3.5 KiB
Go

package backup
import (
"bytes"
"context"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/adminmonitor"
"github.com/portainer/portainer/api/http/offlinegate"
i "github.com/portainer/portainer/api/internal/testhelpers"
"github.com/stretchr/testify/assert"
)
func Test_restoreArchive_usingCombinationOfPasswords(t *testing.T) {
tests := []struct {
name string
backupPassword string
restorePassword string
fails bool
}{
{
name: "empty password to both encrypt and decrypt",
backupPassword: "",
restorePassword: "",
fails: false,
},
{
name: "same password to encrypt and decrypt",
backupPassword: "secret",
restorePassword: "secret",
fails: false,
},
{
name: "different passwords to encrypt and decrypt",
backupPassword: "secret",
restorePassword: "terces",
fails: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
datastore := i.NewDatastore(i.WithUsers([]portainer.User{}), i.WithEdgeJobs([]portainer.EdgeJob{}))
adminMonitor := adminmonitor.New(time.Hour, datastore, context.Background())
h := NewHandler(nil, datastore, offlinegate.NewOfflineGate(), "./test_assets/handler_test", func() {}, adminMonitor)
//backup
archive := backup(t, h, test.backupPassword)
//restore
w := httptest.NewRecorder()
r, err := prepareMultipartRequest(test.restorePassword, archive)
assert.Nil(t, err, "Shouldn't fail to write multipart form")
restoreErr := h.restore(w, r)
assert.Equal(t, test.fails, restoreErr != nil, "Didn't meet expectation of failing restore handler")
})
}
}
func Test_restoreArchive_shouldFailIfSystemWasAlreadyInitialized(t *testing.T) {
admin := portainer.User{
Role: portainer.AdministratorRole,
}
datastore := i.NewDatastore(i.WithUsers([]portainer.User{admin}), i.WithEdgeJobs([]portainer.EdgeJob{}))
adminMonitor := adminmonitor.New(time.Hour, datastore, context.Background())
h := NewHandler(nil, datastore, offlinegate.NewOfflineGate(), "./test_assets/handler_test", func() {}, adminMonitor)
//backup
archive := backup(t, h, "password")
//restore
w := httptest.NewRecorder()
r, err := prepareMultipartRequest("password", archive)
assert.Nil(t, err, "Shouldn't fail to write multipart form")
restoreErr := h.restore(w, r)
assert.NotNil(t, restoreErr, "Should fail, because system it already initialized")
assert.Equal(t, "Cannot restore already initialized instance", restoreErr.Message, "Should fail with certain error")
}
func backup(t *testing.T, h *Handler, password string) []byte {
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(fmt.Sprintf(`{"password":"%s"}`, password)))
w := httptest.NewRecorder()
backupErr := h.backup(w, r)
assert.Nil(t, backupErr, "Backup should not fail")
response := w.Result()
archive, _ := io.ReadAll(response.Body)
return archive
}
func prepareMultipartRequest(password string, file []byte) (*http.Request, error) {
var body bytes.Buffer
w := multipart.NewWriter(&body)
err := w.WriteField("password", password)
if err != nil {
return nil, err
}
fw, err := w.CreateFormFile("file", "filename")
if err != nil {
return nil, err
}
io.Copy(fw, bytes.NewReader(file))
r := httptest.NewRequest(http.MethodPost, "http://localhost/", &body)
r.Header.Set("Content-Type", w.FormDataContentType())
w.Close()
return r, nil
}