285 lines
6.8 KiB
Go
285 lines
6.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
// used for replacing abra code
|
|
"coopcloud.tech/abra/pkg/app"
|
|
"coopcloud.tech/abra/pkg/client"
|
|
"coopcloud.tech/abra/pkg/config"
|
|
containerPkg "coopcloud.tech/abra/pkg/container"
|
|
"coopcloud.tech/abra/pkg/i18n"
|
|
"coopcloud.tech/abra/pkg/service"
|
|
"github.com/docker/docker/api/types"
|
|
containertypes "github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/filters"
|
|
dockerClient "github.com/docker/docker/client"
|
|
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/pkg/stdcopy"
|
|
)
|
|
|
|
func (h *abraHandler) pingBackupBot(w http.ResponseWriter, r *http.Request, server string) {
|
|
cl, err := client.New(server)
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
|
|
_, err = RetrieveBackupBotContainer(cl)
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
func (h *abraHandler) handleBackupCreateApp(w http.ResponseWriter, r *http.Request, appName string) {
|
|
log.Printf("Handling App Backup!")
|
|
args := []string{}
|
|
|
|
_, err := handleBackupCommand(appName, args, "create")
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
|
|
log.Printf("Finishing app backup!")
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func (h *abraHandler) handleBackupRestoreApp(w http.ResponseWriter, r *http.Request, appName string) {
|
|
log.Printf("Handling Backup Restore!")
|
|
|
|
args := []string{}
|
|
d := json.NewDecoder(r.Body)
|
|
d.DisallowUnknownFields()
|
|
|
|
body := struct {
|
|
Snapshot *string `json:"snapshot"`
|
|
}{}
|
|
err := d.Decode(&body)
|
|
if err != nil {
|
|
log.Printf("error: %v", err)
|
|
BadRequest(w, err)
|
|
return
|
|
}
|
|
if body.Snapshot != nil {
|
|
args = append(args, fmt.Sprintf("SNAPSHOT=%s", *body.Snapshot))
|
|
}
|
|
args = append(args, fmt.Sprintf("NONINTERACTIVE=%v", true))
|
|
_, err = handleBackupCommand(appName, args, "restore")
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
|
|
log.Printf("Finishing app restore!")
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func (h *abraHandler) handleBackupListApp(w http.ResponseWriter, r *http.Request, appName string) {
|
|
log.Printf("Handling App List Backup!")
|
|
args := []string{}
|
|
output, err := handleBackupCommand(appName, args, "snapshots")
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
snapshots := strings.Split(output, "\n")
|
|
resp := []AppSnapshot{}
|
|
for _, snapshot := range snapshots {
|
|
items := strings.Split(snapshot, " ")
|
|
if len(items) < 3 {
|
|
break
|
|
}
|
|
resp = append(resp, AppSnapshot{
|
|
CreatedAt: items[0],
|
|
Snapshot: items[1],
|
|
Version: items[2],
|
|
})
|
|
}
|
|
jsonBytes, err := json.Marshal(resp)
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(jsonBytes)
|
|
}
|
|
func (h *abraHandler) handleBackupDownloadSnapshot(w http.ResponseWriter, r *http.Request, appName string) {
|
|
log.Printf("Handling Snapshot Download!")
|
|
app, err := app.Get(appName)
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
cl, err := client.New(app.Server)
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
|
|
targetContainer, err := RetrieveBackupBotContainer(cl)
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
|
|
execEnv := []string{
|
|
fmt.Sprintf("SERVICE=%s", app.Domain),
|
|
"MACHINE_LOGS=true",
|
|
}
|
|
// TODO: add args
|
|
_, err = RunBackupCmdRemote(cl, "download", targetContainer.ID, execEnv)
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
|
|
remoteBackupDir := "/tmp/backup.tar.gz"
|
|
content, size, err := CopyFromContainer(cl, targetContainer.ID, remoteBackupDir)
|
|
if err != nil {
|
|
InternalServerError(w, fmt.Errorf("error copying from container: %v", err))
|
|
}
|
|
defer content.Close()
|
|
|
|
w.Header().Set("Content-type", "application/x-tar")
|
|
tr := tar.NewReader(content)
|
|
for {
|
|
header, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
if filepath.Base(header.Name) == "backup.tar.gz" {
|
|
w.Header().Set(
|
|
"Content-Disposition",
|
|
`attachment; filename="backup.tar.gz"`,
|
|
)
|
|
w.Header().Set("Content-Type", "application/gzip")
|
|
w.Header().Set(
|
|
"Content-Length",
|
|
strconv.FormatInt(size, 10),
|
|
)
|
|
if _, err := io.Copy(w, tr); err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleBackupCommand(appName string, args []string, action string) (string, error) {
|
|
app, err := app.Get(appName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
cl, err := client.New(app.Server)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
targetContainer, err := RetrieveBackupBotContainer(cl)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
execEnv := []string{
|
|
fmt.Sprintf("SERVICE=%s", app.Domain),
|
|
"MACHINE_LOGS=true",
|
|
}
|
|
for _, arg := range args {
|
|
execEnv = append(execEnv, arg)
|
|
}
|
|
output, err := RunBackupCmdRemote(cl, action, targetContainer.ID, execEnv)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return output, nil
|
|
}
|
|
|
|
// copied from https://git.coopcloud.tech/toolshed/abra/src/branch/main/cli/internal/backup.go
|
|
// tty is set to false
|
|
func RunBackupCmdRemote(
|
|
cl *dockerClient.Client,
|
|
backupCmd string,
|
|
containerID string,
|
|
execEnv []string) (string, error) {
|
|
execBackupListOpts := containertypes.ExecOptions{
|
|
AttachStderr: true,
|
|
AttachStdout: true,
|
|
Cmd: []string{"/usr/bin/backup", "--", backupCmd},
|
|
Env: execEnv,
|
|
Tty: false,
|
|
}
|
|
|
|
stdout, err := Exec(context.Background(), cl, containerID, &execBackupListOpts)
|
|
log.Printf("stdout: %v | err: %v", stdout, err)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return stdout, nil
|
|
}
|
|
|
|
func RetrieveBackupBotContainer(cl *dockerClient.Client) (types.Container, error) {
|
|
ctx := context.Background()
|
|
chosenService, err := service.GetServiceByLabel(ctx, cl, config.BackupbotLabel, true)
|
|
if err != nil {
|
|
return types.Container{}, errors.New(i18n.G("no backupbot discovered, is it deployed?"))
|
|
}
|
|
|
|
filters := filters.NewArgs()
|
|
filters.Add("name", chosenService.Spec.Name)
|
|
targetContainer, err := containerPkg.GetContainer(
|
|
ctx,
|
|
cl,
|
|
filters,
|
|
true,
|
|
)
|
|
if err != nil {
|
|
return types.Container{}, err
|
|
}
|
|
|
|
return targetContainer, nil
|
|
}
|
|
|
|
func Exec(ctx context.Context, cli *dockerClient.Client, containerID string, execOptions *container.ExecOptions) (string, error) {
|
|
execResp, err := cli.ContainerExecCreate(ctx, containerID, *execOptions)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
resp, err := cli.ContainerExecAttach(ctx, execResp.ID, container.ExecStartOptions{
|
|
Tty: false,
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Close()
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
_, err = stdcopy.StdCopy(&stdout, &stderr, resp.Reader)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
inspect, err := cli.ContainerExecInspect(ctx, execResp.ID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if inspect.ExitCode != 0 {
|
|
return stdout.String(),
|
|
fmt.Errorf("command exited with status %d and error %s", inspect.ExitCode, stderr.String())
|
|
}
|
|
|
|
return stdout.String(), nil
|
|
}
|