CLI: Improve output of "photoprism config" command #5285

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2025-10-24 13:51:19 +02:00
parent 5ad8a06a44
commit f94219404e
4 changed files with 173 additions and 21 deletions

View File

@@ -115,9 +115,7 @@ func (c *Config) normalizeDatabaseDSN() {
// DatabaseDSN returns the database data source name (DSN). // DatabaseDSN returns the database data source name (DSN).
func (c *Config) DatabaseDSN() string { func (c *Config) DatabaseDSN() string {
c.normalizeDatabaseDSN() if c.NoDatabaseDSN() {
if c.options.DatabaseDSN == "" {
switch c.DatabaseDriver() { switch c.DatabaseDriver() {
case MySQL, MariaDB: case MySQL, MariaDB:
databaseServer := c.DatabaseServer() databaseServer := c.DatabaseServer()
@@ -159,11 +157,33 @@ func (c *Config) DatabaseDSN() string {
return c.options.DatabaseDSN return c.options.DatabaseDSN
} }
// ParseDatabaseDSN parses the database dsn and extracts user, password, database server, and name. // NoDatabaseDSN checks if no manual database data source name (DSN) configuration is set.
func (c *Config) ParseDatabaseDSN() { func (c *Config) NoDatabaseDSN() bool {
c.normalizeDatabaseDSN() c.normalizeDatabaseDSN()
if c.options.DatabaseDSN == "" || c.options.DatabaseServer != "" { return c.options.DatabaseDSN == ""
}
// HasDatabaseDSN checks if a manual database data source name (DSN) configuration is set.
func (c *Config) HasDatabaseDSN() bool {
return !c.NoDatabaseDSN()
}
// ReportDatabaseDSN checks if the database data source name (DSN) should be reported
// instead of database name, server, user, and password.
func (c *Config) ReportDatabaseDSN() bool {
if c.DatabaseDriver() == SQLite3 {
return true
}
return c.HasDatabaseDSN()
}
// ParseDatabaseDSN parses the database dsn and extracts user, password, database server, and name.
func (c *Config) ParseDatabaseDSN() {
if c.NoDatabaseDSN() {
return
} else if c.options.DatabaseServer != "" {
return return
} }

View File

@@ -105,6 +105,26 @@ func TestConfig_ParseDatabaseDSN(t *testing.T) {
assert.Equal(t, "foo:b@r@tcp(honeypot:1234)/baz?charset=utf8mb4,utf8&parseTime=true", c.DatabaseName()) assert.Equal(t, "foo:b@r@tcp(honeypot:1234)/baz?charset=utf8mb4,utf8&parseTime=true", c.DatabaseName())
assert.Equal(t, "", c.DatabaseUser()) assert.Equal(t, "", c.DatabaseUser())
assert.Equal(t, "", c.DatabasePassword()) assert.Equal(t, "", c.DatabasePassword())
t.Run("ManualServerConfig", func(t *testing.T) {
target := NewConfig(CliTestContext())
resetDatabaseOptions(target)
target.options.DatabaseDriver = MySQL
target.options.DatabaseServer = "db.internal:3306"
target.options.DatabaseName = "photoprism"
target.options.DatabaseUser = "app"
target.options.DatabasePassword = "secret"
target.options.DatabaseDSN = "foo:b@r@tcp(otherhost:3307)/other?charset=utf8mb4,utf8&parseTime=true"
target.ParseDatabaseDSN()
assert.Equal(t, "db.internal:3306", target.options.DatabaseServer)
assert.Equal(t, "db.internal", target.DatabaseHost())
assert.Equal(t, "photoprism", target.options.DatabaseName)
assert.Equal(t, "app", target.options.DatabaseUser)
assert.Equal(t, "secret", target.options.DatabasePassword)
})
} }
func TestConfig_DatabaseServer(t *testing.T) { func TestConfig_DatabaseServer(t *testing.T) {
@@ -203,6 +223,43 @@ func TestConfig_DatabaseDSN(t *testing.T) {
assert.Equal(t, "/go/src/github.com/photoprism/photoprism/storage/testdata/index.db?_busy_timeout=5000", c.DatabaseDSN()) assert.Equal(t, "/go/src/github.com/photoprism/photoprism/storage/testdata/index.db?_busy_timeout=5000", c.DatabaseDSN())
} }
func TestConfig_DatabaseDSNFlags(t *testing.T) {
t.Run("NoDatabaseDSN", func(t *testing.T) {
conf := NewConfig(CliTestContext())
resetDatabaseOptions(conf)
assert.True(t, conf.NoDatabaseDSN())
assert.False(t, conf.HasDatabaseDSN())
})
t.Run("DeprecatedDatabaseDsn", func(t *testing.T) {
conf := NewConfig(CliTestContext())
resetDatabaseOptions(conf)
conf.options.DatabaseDriver = MySQL
conf.options.Deprecated.DatabaseDsn = "user:pass@tcp(db.internal:3306)/photoprism"
assert.False(t, conf.NoDatabaseDSN())
assert.True(t, conf.HasDatabaseDSN())
assert.Equal(t, "user:pass@tcp(db.internal:3306)/photoprism", conf.DatabaseDSN())
assert.Empty(t, conf.options.Deprecated.DatabaseDsn)
})
}
func TestConfig_ReportDatabaseDSN(t *testing.T) {
conf := NewConfig(CliTestContext())
resetDatabaseOptions(conf)
assert.Equal(t, SQLite3, conf.DatabaseDriver())
assert.True(t, conf.ReportDatabaseDSN())
conf.options.DatabaseDriver = MySQL
conf.options.DatabaseDSN = ""
assert.False(t, conf.ReportDatabaseDSN())
conf.options.DatabaseDSN = "user:pass@tcp(db.internal:3306)/photoprism"
assert.True(t, conf.ReportDatabaseDSN())
}
func TestConfig_DatabaseFile(t *testing.T) { func TestConfig_DatabaseFile(t *testing.T) {
c := NewConfig(CliTestContext()) c := NewConfig(CliTestContext())
// Ensure SQLite defaults // Ensure SQLite defaults

View File

@@ -13,13 +13,7 @@ import (
func (c *Config) Report() (rows [][]string, cols []string) { func (c *Config) Report() (rows [][]string, cols []string) {
cols = []string{"Name", "Value"} cols = []string{"Name", "Value"}
var dbKey string reportDatabaseDSN := c.ReportDatabaseDSN()
if c.DatabaseDriver() == SQLite3 {
dbKey = "database-dsn"
} else {
dbKey = "database-name"
}
rows = [][]string{ rows = [][]string{
// Authentication. // Authentication.
@@ -219,15 +213,27 @@ func (c *Config) Report() (rows [][]string, cols []string) {
{"http-video-maxage", fmt.Sprintf("%d", c.HttpVideoMaxAge())}, {"http-video-maxage", fmt.Sprintf("%d", c.HttpVideoMaxAge())},
{"http-host", c.HttpHost()}, {"http-host", c.HttpHost()},
{"http-port", fmt.Sprintf("%d", c.HttpPort())}, {"http-port", fmt.Sprintf("%d", c.HttpPort())},
}...)
// Database. // Database.
{"database-driver", c.DatabaseDriver()}, if reportDatabaseDSN {
{dbKey, c.DatabaseName()}, rows = append(rows, [][]string{
{"database-server", c.DatabaseServer()}, {"database-driver", c.DatabaseDriver()},
{"database-host", c.DatabaseHost()}, {"database-dsn", c.DatabaseDSN()},
{"database-port", c.DatabasePortString()}, }...)
{"database-user", c.DatabaseUser()}, } else {
{"database-password", strings.Repeat("*", utf8.RuneCountInString(c.DatabasePassword()))}, rows = append(rows, [][]string{
{"database-driver", c.DatabaseDriver()},
{"database-name", c.DatabaseName()},
{"database-server", c.DatabaseServer()},
{"database-host", c.DatabaseHost()},
{"database-port", c.DatabasePortString()},
{"database-user", c.DatabaseUser()},
{"database-password", strings.Repeat("*", utf8.RuneCountInString(c.DatabasePassword()))},
}...)
}
rows = append(rows, [][]string{
{"database-timeout", fmt.Sprintf("%d", c.DatabaseTimeout())}, {"database-timeout", fmt.Sprintf("%d", c.DatabaseTimeout())},
{"database-conns", fmt.Sprintf("%d", c.DatabaseConns())}, {"database-conns", fmt.Sprintf("%d", c.DatabaseConns())},
{"database-conns-idle", fmt.Sprintf("%d", c.DatabaseConnsIdle())}, {"database-conns-idle", fmt.Sprintf("%d", c.DatabaseConnsIdle())},

View File

@@ -1,6 +1,7 @@
package config package config
import ( import (
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -11,3 +12,71 @@ func TestConfig_Report(t *testing.T) {
r, _ := m.Report() r, _ := m.Report()
assert.GreaterOrEqual(t, len(r), 1) assert.GreaterOrEqual(t, len(r), 1)
} }
func TestConfig_ReportDatabaseSection(t *testing.T) {
collect := func(rows [][]string) map[string]string {
result := make(map[string]string, len(rows))
for _, row := range rows {
if len(row) < 2 {
continue
}
result[row[0]] = row[1]
}
return result
}
t.Run("SQLiteReportsDSN", func(t *testing.T) {
conf := NewConfig(CliTestContext())
resetDatabaseOptions(conf)
rows, _ := conf.Report()
values := collect(rows)
assert.Equal(t, SQLite3, values["database-driver"])
assert.Equal(t, conf.DatabaseDSN(), values["database-dsn"])
_, hasName := values["database-name"]
assert.False(t, hasName)
})
t.Run("MariaDBReportsIndividualFields", func(t *testing.T) {
conf := NewConfig(CliTestContext())
resetDatabaseOptions(conf)
conf.options.DatabaseDriver = MySQL
conf.options.DatabaseServer = "db.internal:3306"
conf.options.DatabaseName = "photoprism"
conf.options.DatabaseUser = "app"
conf.options.DatabasePassword = "secret"
rows, _ := conf.Report()
values := collect(rows)
assert.Equal(t, MySQL, values["database-driver"])
assert.Equal(t, "photoprism", values["database-name"])
assert.Equal(t, "db.internal:3306", values["database-server"])
assert.Equal(t, "db.internal", values["database-host"])
assert.Equal(t, "3306", values["database-port"])
assert.Equal(t, "app", values["database-user"])
assert.Equal(t, strings.Repeat("*", len("secret")), values["database-password"])
_, hasDSN := values["database-dsn"]
assert.False(t, hasDSN)
})
t.Run("MariaDBReportsDSNWhenConfigured", func(t *testing.T) {
conf := NewConfig(CliTestContext())
resetDatabaseOptions(conf)
conf.options.DatabaseDriver = MySQL
conf.options.DatabaseDSN = "user:pass@tcp(db.internal:3306)/photoprism"
rows, _ := conf.Report()
values := collect(rows)
assert.Equal(t, MySQL, values["database-driver"])
assert.Equal(t, "user:pass@tcp(db.internal:3306)/photoprism", values["database-dsn"])
_, hasName := values["database-name"]
assert.False(t, hasName)
_, hasPassword := values["database-password"]
assert.False(t, hasPassword)
})
}