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"})
// Check command output for plausibility.
//t.Logf(output)
// t.Logf(output)
assert.NoError(t, err)
assert.NotContains(t, output, "App Password")
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"})
// Check command output for plausibility.
//t.Logf(output)
// t.Logf(output)
assert.Error(t, err)
assert.Empty(t, output)
@ -44,7 +44,7 @@ func TestAuthAddCommand(t *testing.T) {
output, err := RunWithTestContext(AuthAddCommand, []string{"add", "--scope=test", "--expires=5000", "alice"})
// Check command output for plausibility.
//t.Logf(output)
// t.Logf(output)
assert.Error(t, err)
assert.Empty(t, output)
})
@ -53,7 +53,7 @@ func TestAuthAddCommand(t *testing.T) {
output, err := RunWithTestContext(AuthAddCommand, []string{"add", "--name=test", "--expires=5000", "alice"})
// Check command output for plausibility.
//t.Logf(output)
// t.Logf(output)
assert.Error(t, err)
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.
func readTokenInput(ctx *cli.Context) (string, error) {
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 {
return "", cli.Exit(err, 1)
}

View file

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

View file

@ -10,6 +10,7 @@ import (
"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."
// 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"})
// Check command output for plausibility.
//t.Logf(output)
// t.Logf(output)
assert.NoError(t, err)
assert.Empty(t, output)

View file

@ -23,10 +23,11 @@ const (
ClientRegenerateSecret = "set a new randomly generated client secret"
ClientEnable = "enable client authentication if disabled"
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 (
// ClientRoleUsage describes allowed client roles for CLI help.
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.
var client *entity.Client
client = entity.FindClientByUID(frm.ID())
client := entity.FindClientByUID(frm.ID())
if client == nil {
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"})
// Check command output for plausibility.
//t.Logf(output0)
// t.Logf(output0)
assert.NoError(t, err)
assert.Contains(t, output0, "AuthEnabled │ true")
assert.Contains(t, output0, "oauth2")
@ -28,7 +28,7 @@ func TestClientsModCommand(t *testing.T) {
output, err := RunWithTestContext(ClientsModCommand, []string{"mod", "--disable", "cs7pvt5h8rw9aaqj"})
// Check command output for plausibility.
//t.Logf(output)
// t.Logf(output)
assert.NoError(t, err)
assert.Empty(t, output)
@ -36,7 +36,7 @@ func TestClientsModCommand(t *testing.T) {
output1, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"})
// Check command output for plausibility.
//t.Logf(output1)
// t.Logf(output1)
assert.NoError(t, err)
assert.Contains(t, output1, "AuthEnabled │ false")
@ -51,7 +51,7 @@ func TestClientsModCommand(t *testing.T) {
output3, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"})
// Check command output for plausibility.
//t.Logf(output3)
// t.Logf(output3)
assert.NoError(t, err)
assert.Contains(t, output3, "│ AuthEnabled │ true ")
})
@ -60,7 +60,7 @@ func TestClientsModCommand(t *testing.T) {
output, err := RunWithTestContext(ClientsModCommand, []string{"mod", "--regenerate", "cs7pvt5h8rw9aaqj"})
// Check command output for plausibility.
//t.Logf(output)
// t.Logf(output)
assert.NoError(t, err)
assert.Contains(t, output, "Client Secret")
})

View file

@ -40,9 +40,7 @@ func clientsRemoveAction(ctx *cli.Context) error {
}
// Find client record.
var m *entity.Client
m = entity.FindClientByUID(id)
m := entity.FindClientByUID(id)
if m == nil {
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.
output0, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"})
//t.Logf(output0)
// t.Logf(output0)
assert.NoError(t, err)
assert.NotContains(t, output0, "not found")
assert.Contains(t, output0, "client")
@ -20,14 +20,14 @@ func TestCientsRemoveCommand(t *testing.T) {
output, err := RunWithTestContext(ClientsRemoveCommand, []string{"rm", "cs7pvt5h8rw9aaqj"})
// Check command output for plausibility.
//t.Logf(output)
// t.Logf(output)
assert.NoError(t, err)
assert.Empty(t, output)
// Run command with test context.
output2, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"})
//t.Logf(output2)
// t.Logf(output2)
assert.NoError(t, err)
assert.NotContains(t, output2, "not found")
assert.Contains(t, output2, "client")
@ -36,7 +36,7 @@ func TestCientsRemoveCommand(t *testing.T) {
// Run command with test context.
output0, err := RunWithTestContext(ClientsShowCommand, []string{"show", "cs7pvt5h8rw9aaqj"})
//t.Logf(output0)
// t.Logf(output0)
assert.NoError(t, err)
assert.NotContains(t, output0, "not found")
assert.Contains(t, output0, "client")

View file

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

View file

@ -31,9 +31,7 @@ func clientsShowAction(ctx *cli.Context) error {
}
// Find client record.
var m *entity.Client
m = entity.FindClientByUID(id)
m := entity.FindClientByUID(id)
if m == nil {
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"})
// Check command output for plausibility.
//t.Logf(output)
// t.Logf(output)
assert.NoError(t, err)
assert.Contains(t, output, "Alice")
assert.Contains(t, output, "oauth2")

View file

@ -56,7 +56,7 @@ func clusterNodesListAction(ctx *cli.Context) error {
}
// 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 {
count = 100
}

View file

@ -216,11 +216,12 @@ func clusterNodesRotateAction(ctx *cli.Context) error {
if (resp.Secrets != nil && resp.Secrets.ClientSecret != "") || resp.Database.Password != "" {
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))
} 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, "", ""))
} 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))
}
if resp.Database.DSN != "" {

View file

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

View file

@ -320,22 +320,18 @@ func unzipSafe(zipPath, dest string) error {
if err != nil {
return err
}
defer rc.Close()
// 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 {
rc.Close()
return err
}
defer out.Close()
if _, err := io.Copy(out, rc); err != nil {
out.Close()
rc.Close()
if _, err := io.Copy(out, rc); err != nil { //nolint:gosec // zip entries size is bounded by upstream
return err
}
out.Close()
rc.Close()
}
return nil
}

View file

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

View file

@ -160,9 +160,10 @@ func downloadAction(ctx *cli.Context) error {
impersonate := strings.ToLower(strings.TrimSpace(ctx.String("impersonate")))
if impersonate == "" {
switch impersonate {
case "":
impersonate = "firefox"
} else if impersonate == "none" {
case "none":
impersonate = ""
}
@ -282,14 +283,14 @@ func downloadAction(ctx *cli.Context) error {
}
func() {
defer downloadResult.Close()
f, ferr := os.Create(downloadFilePath)
f, ferr := os.Create(downloadFilePath) //nolint:gosec // download target path chosen by user
if ferr != nil {
log.Errorf("create file failed: %v", ferr)
failures++
return
}
defer f.Close()
if _, cerr := io.Copy(f, downloadResult); cerr != nil {
_ = f.Close()
log.Errorf("write file failed: %v", cerr)
failures++
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\"==\"--dump-single-json\" ( echo {\"id\":\"abc\",\"title\":\"Test\",\"url\":\"http://example.com\",\"_type\":\"video\"} & goto :eof )\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)
}
return path
@ -44,7 +44,7 @@ func createArgsLoggingYtDlp(t *testing.T) string {
b.WriteString("echo '[download]' 1>&2\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)
}
return path
@ -98,7 +98,7 @@ func TestRunDownload_FileMethod_OmitsFormatSort(t *testing.T) {
// Give the logging script a moment to flush in slower environments.
time.Sleep(20 * time.Millisecond)
data, err := os.ReadFile(argsLog)
data, err := os.ReadFile(argsLog) //nolint:gosec // test temp file
if err != nil {
t.Fatalf("reading args log failed: %v", err)
}
@ -157,7 +157,7 @@ func TestRunDownload_FileMethod_WithFormatSort(t *testing.T) {
time.Sleep(20 * time.Millisecond)
data, err := os.ReadFile(argsLog)
data, err := os.ReadFile(argsLog) //nolint:gosec // test temp file
if err != nil {
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() {
defer downloadResult.Close()
f, ferr := os.Create(downloadFilePath)
f, ferr := os.Create(downloadFilePath) //nolint:gosec // download target path chosen by user
if ferr != nil {
log.Errorf("create file failed: %v", ferr)
failures++

View file

@ -74,7 +74,11 @@ func findAction(ctx *cli.Context) error {
rows := make([][]string, 0, len(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)
}

View file

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

View file

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

View file

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

View file

@ -65,9 +65,10 @@ func showConfigAction(ctx *cli.Context) error {
rows, cols := rep.Report(conf)
opt := report.Options{Format: format, NoWrap: rep.NoWrap}
result, _ := report.Render(rows, cols, opt)
if opt.Format == report.Markdown {
switch opt.Format {
case report.Markdown:
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.Println(result)

View file

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

View file

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

View file

@ -57,7 +57,7 @@ func statusAction(ctx *cli.Context) error {
if resp, reqErr := client.Do(req); reqErr != nil {
return fmt.Errorf("cannot connect to %s:%d", conf.HttpHost(), conf.HttpPort())
} 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 {
return readErr
} else {

View file

@ -23,6 +23,7 @@ const (
)
var (
// UserRoleUsage describes allowed user roles for CLI help.
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 err
}
return nil
return entity.AddUser(frm)
})
}

View file

@ -12,7 +12,7 @@ func TestUsersLegacyCommand(t *testing.T) {
output, err := RunWithTestContext(UsersLegacyCommand, []string{""})
// Check command output for plausibility.
//t.Logf(output)
// t.Logf(output)
assert.NoError(t, err)
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"
)
// 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."
// 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"})
// Check command output for plausibility.
//t.Logf(output)
// t.Logf(output)
assert.NoError(t, err)
assert.Empty(t, output)

View file

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

View file

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

View file

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

View file

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