CI: Apply Go linter recommendations to "internal/commands" package #5330

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2025-11-22 17:55:26 +01:00
parent 0e6328a33d
commit 699ad5b50c
38 changed files with 79 additions and 78 deletions

View File

@@ -23,7 +23,7 @@ func TestAuthAddCommand(t *testing.T) {
output, err := RunWithTestContext(AuthAddCommand, []string{"add", "--scope=test", "--expires=5000", "--name=xyz"}) output, err := RunWithTestContext(AuthAddCommand, []string{"add", "--scope=test", "--expires=5000", "--name=xyz"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, output, "App Password") assert.NotContains(t, output, "App Password")
assert.Contains(t, output, "Access Token") assert.Contains(t, output, "Access Token")
@@ -34,7 +34,7 @@ func TestAuthAddCommand(t *testing.T) {
output, err := RunWithTestContext(AuthAddCommand, []string{"add", "--scope=test", "--expires=5000", "xxxxx"}) output, err := RunWithTestContext(AuthAddCommand, []string{"add", "--scope=test", "--expires=5000", "xxxxx"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.Error(t, err) assert.Error(t, err)
assert.Empty(t, output) assert.Empty(t, output)
@@ -44,7 +44,7 @@ func TestAuthAddCommand(t *testing.T) {
output, err := RunWithTestContext(AuthAddCommand, []string{"add", "--scope=test", "--expires=5000", "alice"}) output, err := RunWithTestContext(AuthAddCommand, []string{"add", "--scope=test", "--expires=5000", "alice"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.Error(t, err) assert.Error(t, err)
assert.Empty(t, output) assert.Empty(t, output)
}) })
@@ -53,7 +53,7 @@ func TestAuthAddCommand(t *testing.T) {
output, err := RunWithTestContext(AuthAddCommand, []string{"add", "--name=test", "--expires=5000", "alice"}) output, err := RunWithTestContext(AuthAddCommand, []string{"add", "--name=test", "--expires=5000", "alice"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.Error(t, err) assert.Error(t, err)
assert.Empty(t, output) assert.Empty(t, output)
}) })

View File

@@ -128,7 +128,7 @@ func authJWTInspectAction(ctx *cli.Context) error {
// readTokenInput loads the token from CLI args, file, or STDIN. // readTokenInput loads the token from CLI args, file, or STDIN.
func readTokenInput(ctx *cli.Context) (string, error) { func readTokenInput(ctx *cli.Context) (string, error) {
if file := strings.TrimSpace(ctx.String("file")); file != "" { if file := strings.TrimSpace(ctx.String("file")); file != "" {
data, err := os.ReadFile(file) data, err := os.ReadFile(file) //nolint:gosec // user-supplied path is intended
if err != nil { if err != nil {
return "", cli.Exit(err, 1) return "", cli.Exit(err, 1)
} }

View File

@@ -36,7 +36,7 @@ func TestAuthListCommand(t *testing.T) {
output, err := RunWithTestContext(AuthListCommand, []string{"ls", "--csv", "alice"}) output, err := RunWithTestContext(AuthListCommand, []string{"ls", "--csv", "alice"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, output, "Session ID;") assert.Contains(t, output, "Session ID;")
assert.Contains(t, output, "alice;") assert.Contains(t, output, "alice;")

View File

@@ -10,6 +10,7 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
) )
// AuthResetDescription explains the effect of the auth reset command.
const AuthResetDescription = "This command recreates the auth_sessions database table so that it is compatible with the current version. As a result, all users and clients must re-authenticate. Note that any client access tokens and app passwords that users may have created are also deleted and must be recreated." const AuthResetDescription = "This command recreates the auth_sessions database table so that it is compatible with the current version. As a result, all users and clients must re-authenticate. Note that any client access tokens and app passwords that users may have created are also deleted and must be recreated."
// AuthResetCommand configures the command name, flags, and action. // AuthResetCommand configures the command name, flags, and action.

View File

@@ -21,7 +21,7 @@ func TestAuthResetCommand(t *testing.T) {
output, err := RunWithTestContext(AuthResetCommand, []string{"reset"}) output, err := RunWithTestContext(AuthResetCommand, []string{"reset"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.NoError(t, err) assert.NoError(t, err)
assert.Empty(t, output) assert.Empty(t, output)

View File

@@ -23,10 +23,11 @@ const (
ClientRegenerateSecret = "set a new randomly generated client secret" ClientRegenerateSecret = "set a new randomly generated client secret"
ClientEnable = "enable client authentication if disabled" ClientEnable = "enable client authentication if disabled"
ClientDisable = "disable client authentication" ClientDisable = "disable client authentication"
ClientSecretInfo = "\nPLEASE WRITE DOWN THE %s CLIENT SECRET, AS YOU WILL NOT BE ABLE TO SEE IT AGAIN:" ClientSecretInfo = "\nPLEASE WRITE DOWN THE %s CLIENT SECRET, AS YOU WILL NOT BE ABLE TO SEE IT AGAIN:" //nolint:gosec // informational message only
) )
var ( var (
// ClientRoleUsage describes allowed client roles for CLI help.
ClientRoleUsage = fmt.Sprintf("client authorization `ROLE`, e.g. %s", acl.ClientRoles.CliUsageString()) ClientRoleUsage = fmt.Sprintf("client authorization `ROLE`, e.g. %s", acl.ClientRoles.CliUsageString())
) )

View File

@@ -35,9 +35,7 @@ func clientsModAction(ctx *cli.Context) error {
} }
// Find client record. // Find client record.
var client *entity.Client client := entity.FindClientByUID(frm.ID())
client = entity.FindClientByUID(frm.ID())
if client == nil { if client == nil {
return fmt.Errorf("client %s not found", clean.Log(frm.ID())) return fmt.Errorf("client %s not found", clean.Log(frm.ID()))

View File

@@ -19,7 +19,7 @@ func TestClientsModCommand(t *testing.T) {
output0, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"}) output0, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output0) // t.Logf(output0)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, output0, "AuthEnabled │ true") assert.Contains(t, output0, "AuthEnabled │ true")
assert.Contains(t, output0, "oauth2") assert.Contains(t, output0, "oauth2")
@@ -28,7 +28,7 @@ func TestClientsModCommand(t *testing.T) {
output, err := RunWithTestContext(ClientsModCommand, []string{"mod", "--disable", "cs7pvt5h8rw9aaqj"}) output, err := RunWithTestContext(ClientsModCommand, []string{"mod", "--disable", "cs7pvt5h8rw9aaqj"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.NoError(t, err) assert.NoError(t, err)
assert.Empty(t, output) assert.Empty(t, output)
@@ -36,7 +36,7 @@ func TestClientsModCommand(t *testing.T) {
output1, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"}) output1, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output1) // t.Logf(output1)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, output1, "AuthEnabled │ false") assert.Contains(t, output1, "AuthEnabled │ false")
@@ -51,7 +51,7 @@ func TestClientsModCommand(t *testing.T) {
output3, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"}) output3, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output3) // t.Logf(output3)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, output3, "│ AuthEnabled │ true ") assert.Contains(t, output3, "│ AuthEnabled │ true ")
}) })
@@ -60,7 +60,7 @@ func TestClientsModCommand(t *testing.T) {
output, err := RunWithTestContext(ClientsModCommand, []string{"mod", "--regenerate", "cs7pvt5h8rw9aaqj"}) output, err := RunWithTestContext(ClientsModCommand, []string{"mod", "--regenerate", "cs7pvt5h8rw9aaqj"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, output, "Client Secret") assert.Contains(t, output, "Client Secret")
}) })

View File

@@ -40,9 +40,7 @@ func clientsRemoveAction(ctx *cli.Context) error {
} }
// Find client record. // Find client record.
var m *entity.Client m := entity.FindClientByUID(id)
m = entity.FindClientByUID(id)
if m == nil { if m == nil {
return fmt.Errorf("client %s not found", clean.Log(id)) return fmt.Errorf("client %s not found", clean.Log(id))

View File

@@ -11,7 +11,7 @@ func TestCientsRemoveCommand(t *testing.T) {
// Run command with test context. // Run command with test context.
output0, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"}) output0, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"})
//t.Logf(output0) // t.Logf(output0)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, output0, "not found") assert.NotContains(t, output0, "not found")
assert.Contains(t, output0, "client") assert.Contains(t, output0, "client")
@@ -20,14 +20,14 @@ func TestCientsRemoveCommand(t *testing.T) {
output, err := RunWithTestContext(ClientsRemoveCommand, []string{"rm", "cs7pvt5h8rw9aaqj"}) output, err := RunWithTestContext(ClientsRemoveCommand, []string{"rm", "cs7pvt5h8rw9aaqj"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.NoError(t, err) assert.NoError(t, err)
assert.Empty(t, output) assert.Empty(t, output)
// Run command with test context. // Run command with test context.
output2, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"}) output2, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"})
//t.Logf(output2) // t.Logf(output2)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, output2, "not found") assert.NotContains(t, output2, "not found")
assert.Contains(t, output2, "client") assert.Contains(t, output2, "client")
@@ -36,7 +36,7 @@ func TestCientsRemoveCommand(t *testing.T) {
// Run command with test context. // Run command with test context.
output0, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"}) output0, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"})
//t.Logf(output0) // t.Logf(output0)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, output0, "not found") assert.NotContains(t, output0, "not found")
assert.Contains(t, output0, "client") assert.Contains(t, output0, "client")

View File

@@ -21,7 +21,7 @@ func TestClientsResetCommand(t *testing.T) {
output, err := RunWithTestContext(ClientsResetCommand, []string{"reset"}) output, err := RunWithTestContext(ClientsResetCommand, []string{"reset"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.NoError(t, err) assert.NoError(t, err)
assert.Empty(t, output) assert.Empty(t, output)

View File

@@ -31,9 +31,7 @@ func clientsShowAction(ctx *cli.Context) error {
} }
// Find client record. // Find client record.
var m *entity.Client m := entity.FindClientByUID(id)
m = entity.FindClientByUID(id)
if m == nil { if m == nil {
return fmt.Errorf("client %s not found", clean.Log(id)) return fmt.Errorf("client %s not found", clean.Log(id))

View File

@@ -12,7 +12,7 @@ func TestClientsShowCommand(t *testing.T) {
output, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs5gfen1bgxz7s9i"}) output, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs5gfen1bgxz7s9i"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, output, "Alice") assert.Contains(t, output, "Alice")
assert.Contains(t, output, "oauth2") assert.Contains(t, output, "oauth2")

View File

@@ -56,7 +56,7 @@ func clusterNodesListAction(ctx *cli.Context) error {
} }
// Pagination identical to API defaults. // Pagination identical to API defaults.
count := int(ctx.Uint("count")) count := int(ctx.Uint("count")) //nolint:gosec // CLI flag bounded by validation
if count <= 0 || count > 1000 { if count <= 0 || count > 1000 {
count = 100 count = 100
} }

View File

@@ -216,11 +216,12 @@ func clusterNodesRotateAction(ctx *cli.Context) error {
if (resp.Secrets != nil && resp.Secrets.ClientSecret != "") || resp.Database.Password != "" { if (resp.Secrets != nil && resp.Secrets.ClientSecret != "") || resp.Database.Password != "" {
fmt.Println("PLEASE WRITE DOWN THE FOLLOWING CREDENTIALS; THEY WILL NOT BE SHOWN AGAIN:") fmt.Println("PLEASE WRITE DOWN THE FOLLOWING CREDENTIALS; THEY WILL NOT BE SHOWN AGAIN:")
if resp.Secrets != nil && resp.Secrets.ClientSecret != "" && resp.Database.Password != "" { switch {
case resp.Secrets != nil && resp.Secrets.ClientSecret != "" && resp.Database.Password != "":
fmt.Printf("\n%s\n", report.Credentials("Node Client Secret", resp.Secrets.ClientSecret, "DB Password", resp.Database.Password)) fmt.Printf("\n%s\n", report.Credentials("Node Client Secret", resp.Secrets.ClientSecret, "DB Password", resp.Database.Password))
} else if resp.Secrets != nil && resp.Secrets.ClientSecret != "" { case resp.Secrets != nil && resp.Secrets.ClientSecret != "":
fmt.Printf("\n%s\n", report.Credentials("Node Client Secret", resp.Secrets.ClientSecret, "", "")) fmt.Printf("\n%s\n", report.Credentials("Node Client Secret", resp.Secrets.ClientSecret, "", ""))
} else if resp.Database.Password != "" { case resp.Database.Password != "":
fmt.Printf("\n%s\n", report.Credentials("DB User", resp.Database.User, "DB Password", resp.Database.Password)) fmt.Printf("\n%s\n", report.Credentials("DB User", resp.Database.User, "DB Password", resp.Database.Password))
} }
if resp.Database.DSN != "" { if resp.Database.DSN != "" {

View File

@@ -53,7 +53,7 @@ var (
var ClusterRegisterCommand = &cli.Command{ var ClusterRegisterCommand = &cli.Command{
Name: "register", Name: "register",
Usage: "Registers a node or updates its credentials within a cluster", Usage: "Registers a node or updates its credentials within a cluster",
Flags: append(append([]cli.Flag{ Flags: append([]cli.Flag{
regDryRun, regDryRun,
regNameFlag, regNameFlag,
regRoleFlag, regRoleFlag,
@@ -68,7 +68,7 @@ var ClusterRegisterCommand = &cli.Command{
regRotateSec, regRotateSec,
regWriteConf, regWriteConf,
regForceFlag, regForceFlag,
}, report.CliFlags...)), }, report.CliFlags...),
Action: clusterRegisterAction, Action: clusterRegisterAction,
} }

View File

@@ -320,22 +320,18 @@ func unzipSafe(zipPath, dest string) error {
if err != nil { if err != nil {
return err return err
} }
defer rc.Close()
// Create/truncate target // Create/truncate target
out, err := os.OpenFile(target, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, f.Mode()) out, err := os.OpenFile(target, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, f.Mode()) //nolint:gosec // paths derived from zip entries validated earlier
if err != nil { if err != nil {
rc.Close()
return err return err
} }
defer out.Close()
if _, err := io.Copy(out, rc); err != nil { if _, err := io.Copy(out, rc); err != nil { //nolint:gosec // zip entries size is bounded by upstream
out.Close()
rc.Close()
return err return err
} }
out.Close()
rc.Close()
} }
return nil return nil
} }

View File

@@ -38,6 +38,7 @@ import (
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
) )
// NONINTERACTIVE is the CLI environment flag to disable prompts.
const NONINTERACTIVE = "noninteractive" const NONINTERACTIVE = "noninteractive"
var log = event.Log var log = event.Log

View File

@@ -160,9 +160,10 @@ func downloadAction(ctx *cli.Context) error {
impersonate := strings.ToLower(strings.TrimSpace(ctx.String("impersonate"))) impersonate := strings.ToLower(strings.TrimSpace(ctx.String("impersonate")))
if impersonate == "" { switch impersonate {
case "":
impersonate = "firefox" impersonate = "firefox"
} else if impersonate == "none" { case "none":
impersonate = "" impersonate = ""
} }
@@ -282,14 +283,14 @@ func downloadAction(ctx *cli.Context) error {
} }
func() { func() {
defer downloadResult.Close() defer downloadResult.Close()
f, ferr := os.Create(downloadFilePath) f, ferr := os.Create(downloadFilePath) //nolint:gosec // download target path chosen by user
if ferr != nil { if ferr != nil {
log.Errorf("create file failed: %v", ferr) log.Errorf("create file failed: %v", ferr)
failures++ failures++
return return
} }
defer f.Close()
if _, cerr := io.Copy(f, downloadResult); cerr != nil { if _, cerr := io.Copy(f, downloadResult); cerr != nil {
_ = f.Close()
log.Errorf("write file failed: %v", cerr) log.Errorf("write file failed: %v", cerr)
failures++ failures++
return return

View File

@@ -25,7 +25,7 @@ func createArgsLoggingYtDlp(t *testing.T) string {
b.WriteString(" if \"%%~A\"==\"--version\" ( echo 2025.09.23 & goto :eof )\r\n") b.WriteString(" if \"%%~A\"==\"--version\" ( echo 2025.09.23 & goto :eof )\r\n")
b.WriteString(" if \"%%~A\"==\"--dump-single-json\" ( echo {\"id\":\"abc\",\"title\":\"Test\",\"url\":\"http://example.com\",\"_type\":\"video\"} & goto :eof )\r\n") b.WriteString(" if \"%%~A\"==\"--dump-single-json\" ( echo {\"id\":\"abc\",\"title\":\"Test\",\"url\":\"http://example.com\",\"_type\":\"video\"} & goto :eof )\r\n")
b.WriteString(")\r\n") b.WriteString(")\r\n")
if err := os.WriteFile(path, []byte(b.String()), 0o755); err != nil { if err := os.WriteFile(path, []byte(b.String()), 0o600); err != nil {
t.Fatalf("failed to write fake yt-dlp: %v", err) t.Fatalf("failed to write fake yt-dlp: %v", err)
} }
return path return path
@@ -44,7 +44,7 @@ func createArgsLoggingYtDlp(t *testing.T) string {
b.WriteString("echo '[download]' 1>&2\n") b.WriteString("echo '[download]' 1>&2\n")
b.WriteString("echo 'DATA'\n") b.WriteString("echo 'DATA'\n")
if err := os.WriteFile(path, []byte(b.String()), 0o755); err != nil { if err := os.WriteFile(path, []byte(b.String()), 0o600); err != nil {
t.Fatalf("failed to write fake yt-dlp: %v", err) t.Fatalf("failed to write fake yt-dlp: %v", err)
} }
return path return path
@@ -98,7 +98,7 @@ func TestRunDownload_FileMethod_OmitsFormatSort(t *testing.T) {
// Give the logging script a moment to flush in slower environments. // Give the logging script a moment to flush in slower environments.
time.Sleep(20 * time.Millisecond) time.Sleep(20 * time.Millisecond)
data, err := os.ReadFile(argsLog) data, err := os.ReadFile(argsLog) //nolint:gosec // test temp file
if err != nil { if err != nil {
t.Fatalf("reading args log failed: %v", err) t.Fatalf("reading args log failed: %v", err)
} }
@@ -157,7 +157,7 @@ func TestRunDownload_FileMethod_WithFormatSort(t *testing.T) {
time.Sleep(20 * time.Millisecond) time.Sleep(20 * time.Millisecond)
data, err := os.ReadFile(argsLog) data, err := os.ReadFile(argsLog) //nolint:gosec // test temp file
if err != nil { if err != nil {
t.Fatalf("reading args log failed: %v", err) t.Fatalf("reading args log failed: %v", err)
} }

View File

@@ -184,7 +184,7 @@ func runDownload(conf *config.Config, opts DownloadOpts, inputURLs []string) err
} }
func() { func() {
defer downloadResult.Close() defer downloadResult.Close()
f, ferr := os.Create(downloadFilePath) f, ferr := os.Create(downloadFilePath) //nolint:gosec // download target path chosen by user
if ferr != nil { if ferr != nil {
log.Errorf("create file failed: %v", ferr) log.Errorf("create file failed: %v", ferr)
failures++ failures++

View File

@@ -74,7 +74,11 @@ func findAction(ctx *cli.Context) error {
rows := make([][]string, 0, len(results)) rows := make([][]string, 0, len(results))
for _, found := range results { for _, found := range results {
v := []string{found.FileName, found.FileMime, humanize.Bytes(uint64(found.FileSize)), found.FileHash} size := found.FileSize
if size < 0 {
size = 0
}
v := []string{found.FileName, found.FileMime, humanize.Bytes(uint64(size)), found.FileHash} //nolint:gosec // size non-negative after check
rows = append(rows, v) rows = append(rows, v)
} }

View File

@@ -12,7 +12,7 @@ func TestFindCommand(t *testing.T) {
output, err := RunWithTestContext(FindCommand, []string{"find", "--csv"}) output, err := RunWithTestContext(FindCommand, []string{"find", "--csv"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, output, "File Name;Mime Type;") assert.Contains(t, output, "File Name;Mime Type;")
}) })

View File

@@ -16,6 +16,7 @@ import (
"github.com/photoprism/photoprism/pkg/txt/report" "github.com/photoprism/photoprism/pkg/txt/report"
) )
// MigrationsStatusCommand lists migration status.
var MigrationsStatusCommand = &cli.Command{ var MigrationsStatusCommand = &cli.Command{
Name: "ls", Name: "ls",
Aliases: []string{"status", "show"}, Aliases: []string{"status", "show"},
@@ -25,6 +26,7 @@ var MigrationsStatusCommand = &cli.Command{
Action: migrationsStatusAction, Action: migrationsStatusAction,
} }
// MigrationsRunCommand runs pending migrations.
var MigrationsRunCommand = &cli.Command{ var MigrationsRunCommand = &cli.Command{
Name: "run", Name: "run",
Aliases: []string{"execute", "migrate"}, Aliases: []string{"execute", "migrate"},
@@ -110,15 +112,16 @@ func migrationsStatusAction(ctx *cli.Context) error {
finished = "-" finished = "-"
} }
if m.Error != "" { switch {
case m.Error != "":
info = m.Error info = m.Error
} else if m.Finished() { case m.Finished():
info = "OK" info = "OK"
} else if m.StartedAt.IsZero() { case m.StartedAt.IsZero():
info = "-" info = "-"
} else if m.Repeat(false) { case m.Repeat(false):
info = "Repeat" info = "Repeat"
} else { default:
info = "Running?" info = "Running?"
} }

View File

@@ -131,12 +131,12 @@ func termEcho(on bool) {
Sys: nil} Sys: nil}
var ws syscall.WaitStatus var ws syscall.WaitStatus
cmd := "echo" cmd := "echo"
if on == false { if !on {
cmd = "-echo" cmd = "-echo"
} }
// Enable/disable echoing. // Enable/disable echoing.
pid, err := syscall.ForkExec( pid, err := syscall.ForkExec( //nolint:gosec // uses fixed binary and arguments
"/bin/stty", "/bin/stty",
[]string{"stty", cmd}, []string{"stty", cmd},
&attrs) &attrs)

View File

@@ -65,9 +65,10 @@ func showConfigAction(ctx *cli.Context) error {
rows, cols := rep.Report(conf) rows, cols := rep.Report(conf)
opt := report.Options{Format: format, NoWrap: rep.NoWrap} opt := report.Options{Format: format, NoWrap: rep.NoWrap}
result, _ := report.Render(rows, cols, opt) result, _ := report.Render(rows, cols, opt)
if opt.Format == report.Markdown { switch opt.Format {
case report.Markdown:
fmt.Printf("### %s\n\n", rep.Title) fmt.Printf("### %s\n\n", rep.Title)
} else if opt.Format == report.Default { case report.Default:
fmt.Printf("%s\n\n", strings.ToUpper(rep.Title)) fmt.Printf("%s\n\n", strings.ToUpper(rep.Title))
} }
fmt.Println(result) fmt.Println(result)

View File

@@ -13,7 +13,7 @@ import (
var ShowScopesCommand = &cli.Command{ var ShowScopesCommand = &cli.Command{
Name: "scopes", Name: "scopes",
Usage: "Displays supported authorization scopes", Usage: "Displays supported authorization scopes",
Flags: append(report.CliFlags), Flags: report.CliFlags,
Action: showScopesAction, Action: showScopesAction,
} }

View File

@@ -13,7 +13,7 @@ import (
var ShowSourcesCommand = &cli.Command{ var ShowSourcesCommand = &cli.Command{
Name: "sources", Name: "sources",
Usage: "Displays supported metadata sources and their priorities", Usage: "Displays supported metadata sources and their priorities",
Flags: append(report.CliFlags), Flags: report.CliFlags,
Action: showSourcesAction, Action: showSourcesAction,
} }

View File

@@ -57,7 +57,7 @@ func statusAction(ctx *cli.Context) error {
if resp, reqErr := client.Do(req); reqErr != nil { if resp, reqErr := client.Do(req); reqErr != nil {
return fmt.Errorf("cannot connect to %s:%d", conf.HttpHost(), conf.HttpPort()) return fmt.Errorf("cannot connect to %s:%d", conf.HttpHost(), conf.HttpPort())
} else if resp.StatusCode != 200 { } else if resp.StatusCode != 200 {
return fmt.Errorf("server running at %s:%d, bad status %d\n", conf.HttpHost(), conf.HttpPort(), resp.StatusCode) return fmt.Errorf("server running at %s:%d, bad status %d", conf.HttpHost(), conf.HttpPort(), resp.StatusCode)
} else if body, readErr := io.ReadAll(resp.Body); readErr != nil { } else if body, readErr := io.ReadAll(resp.Body); readErr != nil {
return readErr return readErr
} else { } else {

View File

@@ -23,6 +23,7 @@ const (
) )
var ( var (
// UserRoleUsage describes allowed user roles for CLI help.
UserRoleUsage = fmt.Sprintf("user account `ROLE`, e.g. %s", acl.UserRoles.CliUsageString()) UserRoleUsage = fmt.Sprintf("user account `ROLE`, e.g. %s", acl.UserRoles.CliUsageString())
) )

View File

@@ -134,10 +134,6 @@ func usersAddAction(ctx *cli.Context) error {
} }
} }
if err := entity.AddUser(frm); err != nil { return entity.AddUser(frm)
return err
}
return nil
}) })
} }

View File

@@ -12,7 +12,7 @@ func TestUsersLegacyCommand(t *testing.T) {
output, err := RunWithTestContext(UsersLegacyCommand, []string{""}) output, err := RunWithTestContext(UsersLegacyCommand, []string{""})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, output, "│ ID │ UID │ Name │ User │ Email │ Admin │ Created At │") assert.Contains(t, output, "│ ID │ UID │ Name │ User │ Email │ Admin │ Created At │")
}) })

View File

@@ -9,6 +9,7 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
) )
// UsersResetDescription explains the effect of the users reset command.
const UsersResetDescription = "This command recreates the session and user management database tables so that they are compatible with the current version. Should you experience login problems, for example after an upgrade from an earlier version or a development preview, we recommend that you first try the \"photoprism auth reset --yes\" command to see if it solves the issue. Note that any client access tokens and app passwords that users may have created are also deleted and must be recreated." const UsersResetDescription = "This command recreates the session and user management database tables so that they are compatible with the current version. Should you experience login problems, for example after an upgrade from an earlier version or a development preview, we recommend that you first try the \"photoprism auth reset --yes\" command to see if it solves the issue. Note that any client access tokens and app passwords that users may have created are also deleted and must be recreated."
// UsersResetCommand configures the command name, flags, and action. // UsersResetCommand configures the command name, flags, and action.

View File

@@ -21,7 +21,7 @@ func TestUsersResetCommand(t *testing.T) {
output, err := RunWithTestContext(UsersResetCommand, []string{"reset"}) output, err := RunWithTestContext(UsersResetCommand, []string{"reset"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.NoError(t, err) assert.NoError(t, err)
assert.Empty(t, output) assert.Empty(t, output)

View File

@@ -12,7 +12,7 @@ func TestUsersShowCommand(t *testing.T) {
output, err := RunWithTestContext(UsersShowCommand, []string{"show", "alice"}) output, err := RunWithTestContext(UsersShowCommand, []string{"show", "alice"})
// Check command output for plausibility. // Check command output for plausibility.
//t.Logf(output) // t.Logf(output)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, output, "Alice") assert.Contains(t, output, "Alice")
assert.Contains(t, output, "admin") assert.Contains(t, output, "admin")

View File

@@ -20,7 +20,7 @@ func TestUsersCommand(t *testing.T) {
// Run command with test context. // Run command with test context.
output2, err := RunWithTestContext(UsersShowCommand, []string{"show", "john.admin"}) output2, err := RunWithTestContext(UsersShowCommand, []string{"show", "john.admin"})
//t.Logf(output2) // t.Logf(output2)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, output2, "John") assert.Contains(t, output2, "John")
assert.Contains(t, output2, "admin") assert.Contains(t, output2, "admin")
@@ -38,7 +38,7 @@ func TestUsersCommand(t *testing.T) {
// Run command with test context. // Run command with test context.
output4, err := RunWithTestContext(UsersShowCommand, []string{"show", "john.admin"}) output4, err := RunWithTestContext(UsersShowCommand, []string{"show", "john.admin"})
//t.Logf(output4) // t.Logf(output4)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, output4, "Johnny") assert.Contains(t, output4, "Johnny")
assert.Contains(t, output4, "admin") assert.Contains(t, output4, "admin")
@@ -57,7 +57,7 @@ func TestUsersCommand(t *testing.T) {
// Run command with test context. // Run command with test context.
output6, err := RunWithTestContext(UsersShowCommand, []string{"show", "john.admin"}) output6, err := RunWithTestContext(UsersShowCommand, []string{"show", "john.admin"})
//t.Logf(output6) // t.Logf(output6)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, output6, "Johnny") assert.Contains(t, output6, "Johnny")
assert.Contains(t, output6, "admin") assert.Contains(t, output6, "admin")

View File

@@ -23,6 +23,6 @@ var VisionCommands = &cli.Command{
var VisionSourcesCommand = &cli.Command{ var VisionSourcesCommand = &cli.Command{
Name: "sources", Name: "sources",
Usage: "Displays supported metadata sources and their priorities", Usage: "Displays supported metadata sources and their priorities",
Flags: append(report.CliFlags), Flags: report.CliFlags,
Action: showSourcesAction, Action: showSourcesAction,
} }

View File

@@ -17,7 +17,7 @@ import (
var VisionListCommand = &cli.Command{ var VisionListCommand = &cli.Command{
Name: "ls", Name: "ls",
Usage: "Lists the configured computer vision models", Usage: "Lists the configured computer vision models",
Flags: append(report.CliFlags), Flags: report.CliFlags,
Action: visionListAction, Action: visionListAction,
} }