func Perform(ctx context.Context, docker client.ContainerAPIClient, opts *Options) error {
	if err := opts.Validate(); err != nil {
		return err
	}
	exec, err := docker.ContainerExecCreate(ctx, opts.ContainerID, types.ExecConfig{
		AttachStdout: true,
		AttachStderr: true,
		Tty:          false,
		Cmd:          opts.Commands,
	})
	if err != nil {
		return err
	}
	resp, err := docker.ContainerExecAttach(ctx, exec.ID, types.ExecStartCheck{Tty: false})
	if err != nil {
		return err
	}
	defer resp.Close()
	if err := docker.ContainerExecStart(ctx, exec.ID, types.ExecStartCheck{}); err != nil {
		return fmt.Errorf("unable to start the container exec, %w", err)
	}
	waiting := true
	for waiting {
		resp, err := docker.ContainerExecInspect(ctx, exec.ID)
		if err != nil {
			return err
		}
		waiting = resp.Running
	}
	rdr, stat, err := docker.CopyFromContainer(ctx, opts.ContainerID, "/tmp/"+opts.BackupName)
	if err != nil || !stat.Mode.IsRegular() {
		return err
	}
	defer rdr.Close()
	buf := new(bytes.Buffer)
	tr := tar.NewReader(rdr)
	for {
		_, err := tr.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		if _, err := buf.ReadFrom(tr); err != nil {
			return err
		}
	}
	backupDir := filepath.Join(opts.Home, config.DirectoryName, "backups")
	if err := helpers.MkdirIfNotExists(backupDir); err != nil {
		return err
	}
	dir := filepath.Join(backupDir, opts.ContainerName)
	if err := helpers.MkdirIfNotExists(dir); err != nil {
		return err
	}
	if err := ioutil.WriteFile(filepath.Join(dir, opts.BackupName), buf.Bytes(), 0644); err != nil {
		return err
	}
	return nil
}
Perform is used to perform a backup for a database container, it does not prompt the user as it assumed the Prompt func above
is used to determine the engine (container) and the specific database to backup. Perform accepts the backup commands and is
agnostic to the database engine for the requested backup.
package backup
import (
	"archive/tar"
	"bytes"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/client"
	"github.com/craftcms/nitro/pkg/config"
	"github.com/craftcms/nitro/pkg/containerlabels"
	"github.com/craftcms/nitro/pkg/helpers"
	"github.com/craftcms/nitro/pkg/terminal"
)
type Options struct {
	Home          string
	ContainerID   string
	ContainerName string
	Database      string
	BackupName    string
	Commands      []string
}
func (o *Options) Validate() error {
	if o == nil {
		return fmt.Errorf("options must be provided for the backup")
	}
	if o.BackupName == "" {
		return fmt.Errorf("invalid backup name")
	}
	if o.Commands == nil {
		return fmt.Errorf("invalid commands")
	}
	if o.ContainerID == "" {
		return fmt.Errorf("invalid container id")
	}
	if o.ContainerName == "" {
		return fmt.Errorf("invalid container name")
	}
	if o.Database == "" {
		return fmt.Errorf("invalid database")
	}
	if o.Home == "" {
		return fmt.Errorf("invalid home path")
	}
	return nil
}
func Prompt(ctx context.Context, reader io.Reader, docker client.ContainerAPIClient, output terminal.Outputer, containers []types.Container, containerList []string) (string, string, string, string, error) {
	selected, err := output.Select(reader, "Which database engine? ", containerList)
	if err != nil {
		return "", "", "", "", err
	}
	name := containers[selected].Names[0]
	id := containers[selected].ID
	compatibility := containers[selected].Labels[containerlabels.DatabaseCompatibility]
	databases, err := Databases(ctx, docker, id, compatibility)
	if err != nil {
		return "", "", "", "", err
	}
	var db string
	switch len(databases) {
	case 1:
		output.Info("There is only one database to backup…")
		db = databases[0]
	case 0:
		return "", "", "", "", fmt.Errorf("no databases found")
	default:
		selected, err := output.Select(os.Stdin, "Which database should we backup? ", databases)
		if err != nil {
			return "", "", "", "", err
		}
		db = databases[selected]
	}
	return id, name, compatibility, db, nil
}
func Databases(ctx context.Context, docker client.ContainerAPIClient, containerID, compatibility string) ([]string, error) {
	var commands []string
	if compatibility == "mysql" {
		commands = []string{"mysql", "-unitro", "-pnitro", "-e", `SHOW DATABASES;`}
	} else {
		commands = []string{"psql", "--username=nitro", "--command", `SELECT datname FROM pg_database WHERE datistemplate = false;`}
	}
	exec, err := docker.ContainerExecCreate(ctx, containerID, types.ExecConfig{
		AttachStdout: true,
		AttachStderr: true,
		Tty:          false,
		Cmd:          commands,
	})
	if err != nil {
		return nil, err
	}
	resp, err := docker.ContainerExecAttach(ctx, exec.ID, types.ExecStartCheck{Tty: false})
	if err != nil {
		return nil, err
	}
	defer resp.Close()
	if err := docker.ContainerExecStart(ctx, exec.ID, types.ExecStartCheck{}); err != nil {
		return nil, fmt.Errorf("unable to start the container, %w", err)
	}
	buf := new(bytes.Buffer)
	if _, err := io.Copy(buf, resp.Reader); err != nil {
		return nil, err
	}
	var databases []string
	switch compatibility {
	case "mysql":
		for _, d := range strings.Split(buf.String(), "\n") {
			if d == "Database" || strings.Contains(d, `"Database`) || strings.Contains(d, `?Database`) || strings.Contains(d, `[Database`) || d == "information_schema" || d == "performance_schema" || d == "sys" || strings.Contains(d, "password on the command line") || d == "mysql" || d == "" {
				continue
			}
			databases = append(databases, strings.TrimSpace(d))
		}
	default:
		// get all the databases from the postgres engine
		sp := strings.Split(buf.String(), "\n")
		for i, d := range sp {
			// remove the first, second, last, rows, and empty lines
			if i == 0 || i == 1 || i == len(sp) || strings.Contains(d, "rows)") || d == "" {
				continue
			}
			databases = append(databases, strings.TrimSpace(d))
		}
	}
	return databases, nil
}