diff --git a/cmd/dump_test.go b/cmd/dump_test.go index 0be5ce62..3b9a160b 100644 --- a/cmd/dump_test.go +++ b/cmd/dump_test.go @@ -40,6 +40,20 @@ func TestDumpCmd(t *testing.T) { DBConn: database.Connection{Host: "abc", Port: defaultPort}, FilenamePattern: "db_backup_{{ .now }}.{{ .compression }}", }, core.TimerOptions{Frequency: defaultFrequency, Begin: defaultBegin}, nil}, + {"file URL with pass-file", []string{"--server", "abc", "--target", "file:///foo/bar", "--pass-file", "testdata/password.txt"}, "", false, core.DumpOptions{ + Targets: []storage.Storage{file.New(*fileTargetURL)}, + MaxAllowedPacket: defaultMaxAllowedPacket, + Compressor: &compression.GzipCompressor{}, + DBConn: database.Connection{Host: "abc", Port: defaultPort, Pass: "testpassword"}, + FilenamePattern: "db_backup_{{ .now }}.{{ .compression }}", + }, core.TimerOptions{Frequency: defaultFrequency, Begin: defaultBegin}, nil}, + {"file URL with pass and pass-file (pass takes precedence)", []string{"--server", "abc", "--target", "file:///foo/bar", "--pass", "explicitpass", "--pass-file", "testdata/password.txt"}, "", false, core.DumpOptions{ + Targets: []storage.Storage{file.New(*fileTargetURL)}, + MaxAllowedPacket: defaultMaxAllowedPacket, + Compressor: &compression.GzipCompressor{}, + DBConn: database.Connection{Host: "abc", Port: defaultPort, Pass: "explicitpass"}, + FilenamePattern: "db_backup_{{ .now }}.{{ .compression }}", + }, core.TimerOptions{Frequency: defaultFrequency, Begin: defaultBegin}, nil}, {"file URL with prune", []string{"--server", "abc", "--target", "file:///foo/bar", "--retention", "1h"}, "", false, core.DumpOptions{ Targets: []storage.Storage{file.New(*fileTargetURL)}, MaxAllowedPacket: defaultMaxAllowedPacket, diff --git a/cmd/root.go b/cmd/root.go index 41f19f2d..3e6b28e5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -167,6 +167,18 @@ func rootCmd(execs execs) (*cobra.Command, error) { cmdConfig.dbconn.Pass = dbPass } + // read password from file if pass is not set and pass-file is set + if cmdConfig.dbconn.Pass == "" { + dbPassFile := v.GetString("pass-file") + if dbPassFile != "" { + passBytes, err := os.ReadFile(dbPassFile) + if err != nil { + return fmt.Errorf("failed to read password from file %s: %w", dbPassFile, err) + } + cmdConfig.dbconn.Pass = strings.TrimSpace(string(passBytes)) + } + } + // these are not from the config file, as they are generic credentials, used across all targets. // the config file uses specific ones per target cmdConfig.creds = credentials.Creds{ @@ -222,6 +234,9 @@ func rootCmd(execs execs) (*cobra.Command, error) { // pass via CLI or env var pflags.String("pass", "", "password for database server") + // pass-file via CLI or env var + pflags.String("pass-file", "", "path to file containing password for database server") + // debug via CLI or env var or default pflags.IntP("verbose", "v", 0, "set log level, 1 is debug, 2 is trace") pflags.Bool("debug", false, "set log level to debug, equivalent of --verbose=1; if both set, --version always overrides") diff --git a/cmd/testdata/password.txt b/cmd/testdata/password.txt new file mode 100644 index 00000000..5f978152 --- /dev/null +++ b/cmd/testdata/password.txt @@ -0,0 +1 @@ +testpassword diff --git a/docs/configuration.md b/docs/configuration.md index 24b037d4..e6d3c95f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -68,6 +68,7 @@ The following are the environment variables, CLI flags and configuration file op | port to use to connect to database. Optional. | BR | `port` | `DB_PORT` | `database.port` | 3306 | | username for the database | BR | `user` | `DB_USER` | `database.credentials.username` | | | password for the database | BR | `pass` | `DB_PASS` | `database.credentials.password` | | +| path to file containing password for the database. `pass` takes precedence if both are set. | BR | `pass-file` | `DB_PASS_FILE` | | | | names of databases to dump, comma-separated | B | `include` | `DB_DUMP_INCLUDE` | `dump.include` | all databases in the server | | names of databases to exclude from the dump | B | `exclude` | `DB_DUMP_EXCLUDE` | `dump.exclude` | | | do not include `USE ;` statement in the dump | B | `no-database-name` | `NO_DATABASE_NAME` | `dump.noDatabaseName` | `false` |