sq/cli/cmd_db_restore.go
Neil O'Toole fabc46c758
Switched more flags over to options mechanism (#394)
* Switched more flags over to options mechanism

* flag.IngestHeader

* flag.CSVEmptyAsNull and flag.CSVDelim

* flag.PingTimeout

* more flags

* flag.LogEnabled

* logging flags
2024-02-09 11:54:37 -07:00

260 lines
8.3 KiB
Go

package cli
import (
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/neilotoole/sq/cli/flag"
"github.com/neilotoole/sq/cli/run"
"github.com/neilotoole/sq/drivers/postgres"
"github.com/neilotoole/sq/libsq/core/errz"
"github.com/neilotoole/sq/libsq/core/execz"
"github.com/neilotoole/sq/libsq/core/lg"
"github.com/neilotoole/sq/libsq/core/lg/lga"
"github.com/neilotoole/sq/libsq/source"
"github.com/neilotoole/sq/libsq/source/drivertype"
)
func newDBRestoreCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "restore",
Short: "Restore db catalog or cluster from dump",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
return cmd
}
func newDBRestoreCatalogCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "catalog @src [--from file.dump] [--print]",
Short: "Restore db catalog from dump",
Long: `Restore into @src from dump file, using the db-native restore tool.
If --from is specified, the dump is read from that file; otherwise stdin is used.
When --no-owner is specified, the source user will own the restored objects: the
ownership (and ACLs) from the dump file are disregarded.
If --print or --print-long are specified, the restore command is not executed, but
instead the db-native command is printed to stdout. Note that the command output
will include DB credentials. For a Postgres source, it would look something like:
pg_restore -d 'postgres://alice:abc123@localhost:5432/sales' backup.dump`,
Args: cobra.ExactArgs(1),
ValidArgsFunction: completeHandle(1, true),
RunE: execDBRestoreCatalog,
Example: ` # Restore @sakila_pg from backup.dump
$ sq db restore catalog @sakila_pg -f backup.dump
# With verbose output, and reading from stdin
$ sq db restore catalog -v @sakila_pg < backup.dump
# Don't use ownership from dump; the source user will own the restored objects
$ sq db restore catalog @sakila_pg --no-owner < backup.dump
# Print the db-native restore command, but don't execute it
$ sq db restore catalog @sakila_pg -f backup.dump --print`,
}
cmdMarkPlainStdout(cmd)
// FIXME: add --src.Schema
cmd.Flags().StringP(flag.DBRestoreFrom, flag.DBRestoreFromShort, "", flag.DBRestoreFromUsage)
cmd.Flags().Bool(flag.DBRestoreNoOwner, false, flag.DBRestoreNoOwnerUsage)
cmd.Flags().StringP(flag.FileOutput, flag.FileOutputShort, "", flag.FileOutputUsage)
cmd.Flags().Bool(flag.DBPrintToolCmd, false, flag.DBPrintToolCmdUsage)
cmd.Flags().Bool(flag.DBPrintLongToolCmd, false, flag.DBPrintLongToolCmdUsage)
cmd.MarkFlagsMutuallyExclusive(flag.DBPrintToolCmd, flag.DBPrintLongToolCmd)
return cmd
}
func execDBRestoreCatalog(cmd *cobra.Command, args []string) error {
var (
ru = run.FromContext(cmd.Context())
src *source.Source
err error
// fpDump is the (optional) path to the dump file.
// If empty, stdin is used.
dumpFile string
)
if src, err = ru.Config.Collection.Get(args[0]); err != nil {
return err
}
errPrefix := "db restore catalog: " + src.Handle
if cmdFlagChanged(cmd, flag.DBRestoreFrom) {
if dumpFile = strings.TrimSpace(cmd.Flag(flag.DBRestoreFrom).Value.String()); dumpFile == "" {
return errz.Errorf("%s: %s is specified, but empty", errPrefix, flag.DBRestoreFrom)
}
}
if err = applySourceOptions(cmd, src); err != nil {
return err
}
verbose := OptVerbose.Get(src.Options)
noOwner := cmdFlagBool(cmd, flag.DBRestoreNoOwner)
var execCmd *execz.Cmd
switch src.Type { //nolint:exhaustive
case drivertype.Pg:
params := &postgres.ToolParams{
Verbose: verbose,
NoOwner: noOwner,
File: dumpFile,
}
execCmd, err = postgres.RestoreCatalogCmd(src, params)
default:
return errz.Errorf("%s: not supported for %s", errPrefix, src.Type)
}
if err != nil {
return errz.Wrap(err, errPrefix)
}
execCmd.NoProgress = !OptProgress.Get(src.Options)
execCmd.Label = src.Handle + ": " + execCmd.Name
execCmd.Stdin = ru.Stdin
execCmd.Stdout = ru.Stdout
execCmd.Stderr = ru.ErrOut
execCmd.ErrPrefix = errPrefix
if cmdFlagBool(cmd, flag.DBPrintToolCmd) || cmdFlagBool(cmd, flag.DBPrintLongToolCmd) {
lg.FromContext(cmd.Context()).Info("Printing external cmd", lga.Cmd, execCmd)
_, err = fmt.Fprintln(ru.Out, execCmd.String())
return errz.Err(err)
}
switch src.Type { //nolint:exhaustive
case drivertype.Pg:
lg.FromContext(cmd.Context()).Info("Executing external cmd", lga.Cmd, execCmd)
return execz.Exec(cmd.Context(), execCmd)
default:
return errz.Errorf("%s: cmd not supported for %s", errPrefix, src.Type)
}
}
func newDBRestoreClusterCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "cluster @src [--from file.dump] [--print]",
Short: "Restore db cluster from dump",
Long: `Restore entire db cluster into @src from dump file, using the db-native restore
tool.
If --from is specified, the dump is read from that file; otherwise stdin is used.
When --no-owner is specified, the source user will own the restored objects: the
ownership (and ACLs) from the dump file are disregarded.
If --print or --print-long are specified, the restore command is not executed, but
instead the db-native command is printed to stdout. Note that the command output
will include DB credentials. For a Postgres source, it would look something like:
FIXME: example command
psql -d 'postgres://alice:abc123@localhost:5432/sales' backup.dump`,
Args: cobra.ExactArgs(1),
ValidArgsFunction: completeHandle(1, true),
RunE: execDBRestoreCluster,
Example: ` # Restore @sakila_pg from backup.dump
$ sq db restore cluster @sakila_pg -f backup.dump
# With verbose output, and reading from stdin
$ sq db restore cluster -v @sakila_pg < backup.dump
# Don't use ownership from dump; the source user will own the restored objects
$ sq db restore cluster @sakila_pg --no-owner < backup.dump
# Print the db-native restore command, but don't execute it
$ sq db restore cluster @sakila_pg -f backup.dump --print`,
}
cmdMarkPlainStdout(cmd)
cmd.Flags().StringP(flag.DBRestoreFrom, flag.DBRestoreFromShort, "", flag.DBRestoreFromUsage)
cmd.Flags().Bool(flag.DBRestoreNoOwner, false, flag.DBRestoreNoOwnerUsage)
cmd.Flags().StringP(flag.FileOutput, flag.FileOutputShort, "", flag.FileOutputUsage)
cmd.Flags().Bool(flag.DBPrintToolCmd, false, flag.DBPrintToolCmdUsage)
cmd.Flags().Bool(flag.DBPrintLongToolCmd, false, flag.DBPrintLongToolCmdUsage)
cmd.MarkFlagsMutuallyExclusive(flag.DBPrintToolCmd, flag.DBPrintLongToolCmd)
return cmd
}
func execDBRestoreCluster(cmd *cobra.Command, args []string) error {
var (
ru = run.FromContext(cmd.Context())
src *source.Source
err error
// dumpFile is the (optional) path to the dump file.
// If empty, stdin is used.
dumpFile string
)
if src, err = ru.Config.Collection.Get(args[0]); err != nil {
return err
}
errPrefix := "db restore cluster: " + src.Handle
if cmdFlagChanged(cmd, flag.DBRestoreFrom) {
if dumpFile = strings.TrimSpace(cmd.Flag(flag.DBRestoreFrom).Value.String()); dumpFile == "" {
return errz.Errorf("%s: %s is specified, but empty", errPrefix, flag.DBRestoreFrom)
}
}
if err = applySourceOptions(cmd, src); err != nil {
return err
}
verbose := OptVerbose.Get(src.Options)
// FIXME: get rid of noOwner from this command?
// noOwner := cmdFlagBool(cmd, flag.RestoreNoOwner)
var execCmd *execz.Cmd
switch src.Type { //nolint:exhaustive
case drivertype.Pg:
params := &postgres.ToolParams{
Verbose: verbose,
File: dumpFile,
}
execCmd, err = postgres.RestoreClusterCmd(src, params)
default:
return errz.Errorf("%s: not supported for %s", errPrefix, src.Type)
}
if err != nil {
return errz.Wrap(err, errPrefix)
}
execCmd.NoProgress = !OptProgress.Get(src.Options)
execCmd.Label = src.Handle + ": " + execCmd.Name
execCmd.Stdin = ru.Stdin
execCmd.Stdout = ru.Stdout
execCmd.Stderr = ru.ErrOut
execCmd.ErrPrefix = errPrefix
if cmdFlagBool(cmd, flag.DBPrintToolCmd) || cmdFlagBool(cmd, flag.DBPrintLongToolCmd) {
lg.FromContext(cmd.Context()).Info("Printing external cmd", lga.Cmd, execCmd)
s := execCmd.String()
_, err = fmt.Fprintln(ru.Out, s)
return errz.Err(err)
}
switch src.Type { //nolint:exhaustive
case drivertype.Pg:
lg.FromContext(cmd.Context()).Info("Executing external cmd", lga.Cmd, execCmd)
return execz.Exec(cmd.Context(), execCmd)
default:
return errz.Errorf("%s: not supported for %s", errPrefix, src.Type)
}
}