Skip to content

Commit 9d968ab

Browse files
committed
CLI: Refactor "dry-run" and "yes" command flags to use helper functions
Signed-off-by: Michael Mayer <[email protected]>
1 parent f125bfd commit 9d968ab

File tree

8 files changed

+124
-36
lines changed

8 files changed

+124
-36
lines changed

internal/commands/cleanup.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ var CleanUpCommand = &cli.Command{
2020
}
2121

2222
var cleanUpFlags = []cli.Flag{
23-
&cli.BoolFlag{
24-
Name: "dry",
25-
Usage: "performs a dry run that doesn't actually remove anything",
26-
},
23+
DryRunFlag("performs a dry run that doesn't actually remove anything"),
2724
}
2825

2926
// cleanUpAction removes orphaned index entries, sidecar and thumbnail files.
@@ -49,7 +46,7 @@ func cleanUpAction(ctx *cli.Context) error {
4946
w := get.CleanUp()
5047

5148
opt := photoprism.CleanUpOptions{
52-
Dry: ctx.Bool("dry"),
49+
Dry: ctx.Bool("dry-run"),
5350
}
5451

5552
// Start cleanup worker.

internal/commands/cluster_nodes_mod.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ var ClusterNodesModCommand = &cli.Command{
2424
Name: "mod",
2525
Usage: "Updates node properties",
2626
ArgsUsage: "<id|name>",
27-
Flags: []cli.Flag{nodesModRoleFlag, nodesModInternal, nodesModLabel, &cli.BoolFlag{Name: "yes", Aliases: []string{"y"}, Usage: "runs the command non-interactively"}},
28-
Hidden: true, // Required for cluster-management only.
29-
Action: clusterNodesModAction,
27+
Flags: []cli.Flag{nodesModRoleFlag, nodesModInternal, nodesModLabel,
28+
DryRunFlag("preview updates without modifying the registry"),
29+
YesFlag(),
30+
},
31+
Hidden: true, // Required for cluster-management only.
32+
Action: clusterNodesModAction,
3033
}
3134

3235
func clusterNodesModAction(ctx *cli.Context) error {
@@ -62,11 +65,15 @@ func clusterNodesModAction(ctx *cli.Context) error {
6265
return cli.Exit(fmt.Errorf("node not found"), 3)
6366
}
6467

68+
changes := make([]string, 0, 4)
69+
6570
if v := ctx.String("role"); v != "" {
6671
n.Role = clean.TypeLowerDash(v)
72+
changes = append(changes, fmt.Sprintf("role=%s", clean.Log(n.Role)))
6773
}
6874
if v := ctx.String("advertise-url"); v != "" {
6975
n.AdvertiseUrl = v
76+
changes = append(changes, fmt.Sprintf("advertise-url=%s", clean.Log(n.AdvertiseUrl)))
7077
}
7178
if labels := ctx.StringSlice("label"); len(labels) > 0 {
7279
if n.Labels == nil {
@@ -77,6 +84,16 @@ func clusterNodesModAction(ctx *cli.Context) error {
7784
n.Labels[k] = v
7885
}
7986
}
87+
changes = append(changes, fmt.Sprintf("labels+=%s", clean.Log(strings.Join(labels, ","))))
88+
}
89+
90+
if ctx.Bool("dry-run") {
91+
if len(changes) == 0 {
92+
log.Infof("dry-run: no updates to apply for node %s", clean.LogQuote(n.Name))
93+
} else {
94+
log.Infof("dry-run: would update node %s (%s)", clean.LogQuote(n.Name), strings.Join(changes, ", "))
95+
}
96+
return nil
8097
}
8198

8299
confirmed := RunNonInteractively(ctx.Bool("yes"))

internal/commands/cluster_nodes_remove.go

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ var ClusterNodesRemoveCommand = &cli.Command{
2020
Usage: "Deletes a node from the registry",
2121
ArgsUsage: "<id|name>",
2222
Flags: []cli.Flag{
23-
&cli.BoolFlag{Name: "yes", Aliases: []string{"y"}, Usage: "runs the command non-interactively"},
23+
YesFlag(),
2424
&cli.BoolFlag{Name: "all-ids", Usage: "delete all records that share the same UUID (admin cleanup)"},
2525
&cli.BoolFlag{Name: "drop-db", Aliases: []string{"d"}, Usage: "drop the node’s provisioned database and user after registry deletion"},
26+
DryRunFlag("preview deletion without modifying the registry or database"),
2627
},
2728
Hidden: true, // Required for cluster-management only.
2829
Action: clusterNodesRemoveAction,
@@ -66,33 +67,54 @@ func clusterNodesRemoveAction(ctx *cli.Context) error {
6667

6768
uuid := node.UUID
6869

69-
confirmed := RunNonInteractively(ctx.Bool("yes"))
70-
if !confirmed {
71-
prompt := promptui.Prompt{Label: fmt.Sprintf("Delete node %s?", clean.Log(uuid)), IsConfirm: true}
72-
if _, err := prompt.Run(); err != nil {
73-
log.Infof("node %s was not deleted", clean.Log(uuid))
74-
return nil
75-
}
76-
}
77-
7870
dropDB := ctx.Bool("drop-db")
7971
dbName, dbUser := "", ""
72+
8073
if dropDB && node.Database != nil {
8174
dbName = node.Database.Name
8275
dbUser = node.Database.User
8376
}
8477

78+
if ctx.Bool("dry-run") {
79+
log.Infof("dry-run: would delete node %s (uuid=%s, clientId=%s)", clean.LogQuote(node.Name), clean.Log(uuid), clean.Log(node.ClientID))
80+
81+
if ctx.Bool("all-ids") {
82+
log.Infof("dry-run: would remove all registry entries that share uuid %s", clean.Log(uuid))
83+
}
84+
85+
if dropDB {
86+
if dbName == "" && dbUser == "" {
87+
log.Infof("dry-run: --drop-db requested but no database credentials are recorded for node %s", clean.LogQuote(node.Name))
88+
} else {
89+
log.Infof("dry-run: would drop database %s and user %s", clean.Log(dbName), clean.Log(dbUser))
90+
}
91+
}
92+
93+
return nil
94+
}
95+
96+
if !RunNonInteractively(ctx.Bool("yes")) {
97+
prompt := promptui.Prompt{Label: fmt.Sprintf("Delete node %s?", clean.Log(uuid)), IsConfirm: true}
98+
if _, err = prompt.Run(); err != nil {
99+
log.Infof("node %s was not deleted", clean.Log(uuid))
100+
return nil
101+
}
102+
}
103+
85104
if ctx.Bool("all-ids") {
86-
if err := r.DeleteAllByUUID(uuid); err != nil {
105+
if err = r.DeleteAllByUUID(uuid); err != nil {
87106
return cli.Exit(err, 1)
88107
}
89-
} else if err := r.Delete(uuid); err != nil {
108+
} else if err = r.Delete(uuid); err != nil {
90109
return cli.Exit(err, 1)
91110
}
92111

112+
loggedDeletion := false
113+
93114
if dropDB {
94115
if dbName == "" && dbUser == "" {
95116
log.Infof("node %s has been deleted (no database credentials recorded)", clean.Log(uuid))
117+
loggedDeletion = true
96118
} else {
97119
dropCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
98120
defer cancel()
@@ -103,7 +125,9 @@ func clusterNodesRemoveAction(ctx *cli.Context) error {
103125
}
104126
}
105127

106-
log.Infof("node %s has been deleted", clean.Log(uuid))
128+
if !loggedDeletion {
129+
log.Infof("node %s has been deleted", clean.Log(uuid))
130+
}
107131

108132
return nil
109133
})

internal/commands/cluster_nodes_rotate.go

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/photoprism/photoprism/internal/service/cluster"
1414
reg "github.com/photoprism/photoprism/internal/service/cluster/registry"
1515
"github.com/photoprism/photoprism/pkg/clean"
16+
"github.com/photoprism/photoprism/pkg/txt"
1617
"github.com/photoprism/photoprism/pkg/txt/report"
1718
)
1819

@@ -28,8 +29,11 @@ var ClusterNodesRotateCommand = &cli.Command{
2829
Name: "rotate",
2930
Usage: "Rotates a node's DB and/or secret via Portal (HTTP)",
3031
ArgsUsage: "<id|name>",
31-
Flags: append([]cli.Flag{rotateDatabaseFlag, rotateSecretFlag, &cli.BoolFlag{Name: "yes", Aliases: []string{"y"}, Usage: "runs the command non-interactively"}, rotatePortalURL, rotatePortalTok}, report.CliFlags...),
32-
Action: clusterNodesRotateAction,
32+
Flags: append([]cli.Flag{rotateDatabaseFlag, rotateSecretFlag,
33+
DryRunFlag("preview rotation without contacting the Portal"),
34+
YesFlag(),
35+
rotatePortalURL, rotatePortalTok}, report.CliFlags...),
36+
Action: clusterNodesRotateAction,
3337
}
3438

3539
func clusterNodesRotateAction(ctx *cli.Context) error {
@@ -64,24 +68,48 @@ func clusterNodesRotateAction(ctx *cli.Context) error {
6468
if portalURL == "" {
6569
portalURL = os.Getenv(config.EnvVar("portal-url"))
6670
}
67-
if portalURL == "" {
68-
return cli.Exit(fmt.Errorf("portal URL is required (use --portal-url or set portal-url)"), 2)
69-
}
7071
token := ctx.String("join-token")
7172
if token == "" {
7273
token = conf.JoinToken()
7374
}
7475
if token == "" {
7576
token = os.Getenv(config.EnvVar("join-token"))
7677
}
77-
if token == "" {
78-
return cli.Exit(fmt.Errorf("portal token is required (use --join-token or set join-token)"), 2)
79-
}
8078

8179
// Default: rotate DB only if no flag given (safer default)
8280
rotateDatabase := ctx.Bool("database") || (!ctx.IsSet("database") && !ctx.IsSet("secret"))
8381
rotateSecret := ctx.Bool("secret")
8482

83+
if ctx.Bool("dry-run") {
84+
target := clean.LogQuote(name)
85+
if target == "" {
86+
target = "(unnamed node)"
87+
}
88+
var what []string
89+
if rotateDatabase {
90+
what = append(what, "database credentials")
91+
}
92+
if rotateSecret {
93+
what = append(what, "node secret")
94+
}
95+
if len(what) == 0 {
96+
what = append(what, "no resources (no rotation flags set)")
97+
}
98+
if portalURL == "" {
99+
log.Infof("dry-run: would rotate %s for %s (portal URL not set)", txt.JoinAnd(what), target)
100+
} else {
101+
log.Infof("dry-run: would rotate %s for %s via %s", txt.JoinAnd(what), target, clean.Log(portalURL))
102+
}
103+
return nil
104+
}
105+
106+
if portalURL == "" {
107+
return cli.Exit(fmt.Errorf("portal URL is required (use --portal-url or set portal-url)"), 2)
108+
}
109+
if token == "" {
110+
return cli.Exit(fmt.Errorf("portal token is required (use --join-token or set join-token)"), 2)
111+
}
112+
85113
confirmed := RunNonInteractively(ctx.Bool("yes"))
86114
if !confirmed {
87115
var what string

internal/commands/cluster_register.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ var (
3737
regPortalTok = &cli.StringFlag{Name: "join-token", Usage: "Portal access `TOKEN` (defaults to config)"}
3838
regWriteConf = &cli.BoolFlag{Name: "write-config", Usage: "persists returned secrets and DB settings to local config"}
3939
regForceFlag = &cli.BoolFlag{Name: "force", Aliases: []string{"f"}, Usage: "confirm actions that may overwrite/replace local data (e.g., --write-config)"}
40-
regDryRun = &cli.BoolFlag{Name: "dry-run", Usage: "print derived values and payload without performing registration"}
40+
regDryRun = DryRunFlag("print derived values and payload without performing registration")
4141
)
4242

4343
// ClusterRegisterCommand registers a node with the Portal via HTTP.

internal/commands/flags.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ func JsonFlag() *cli.BoolFlag {
1414
return &cli.BoolFlag{Name: "json", Aliases: []string{"j"}, Usage: "print machine-readable JSON"}
1515
}
1616

17+
// DryRunFlag returns the shared CLI flag definition for dry runs across commands.
18+
func DryRunFlag(usage string) *cli.BoolFlag {
19+
if usage == "" {
20+
usage = "performs a dry run without making any destructive changes"
21+
}
22+
return &cli.BoolFlag{Name: "dry-run", Aliases: []string{"dry"}, Usage: usage}
23+
}
24+
25+
// YesFlag returns the shared CLI flag definition for non-interactive runs across commands.
26+
func YesFlag() *cli.BoolFlag {
27+
return &cli.BoolFlag{Name: "yes", Aliases: []string{"y"}, Usage: "runs the command non-interactively"}
28+
}
29+
1730
// PicturesCountFlag returns a shared flag definition limiting how many pictures a batch operation processes.
1831
// Usage: commands from the vision or import tooling that need to cap result size per invocation.
1932
func PicturesCountFlag() *cli.IntFlag {

internal/commands/purge.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ var purgeFlags = []cli.Flag{
2828
Name: "hard",
2929
Usage: "permanently removes data from the index",
3030
},
31-
&cli.BoolFlag{
32-
Name: "dry",
33-
Usage: "performs a dry run that doesn't actually remove anything",
34-
},
31+
DryRunFlag("performs a dry run that doesn't actually remove anything"),
3532
}
3633

3734
// purgeAction removes missing files from search results
@@ -67,7 +64,7 @@ func purgeAction(ctx *cli.Context) error {
6764

6865
opt := photoprism.PurgeOptions{
6966
Path: subPath,
70-
Dry: ctx.Bool("dry"),
67+
Dry: ctx.Bool("dry-run"),
7168
Hard: ctx.Bool("hard"),
7269
Force: true,
7370
}

internal/commands/vision_run.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ var VisionRunCommand = &cli.Command{
3030
Aliases: []string{"f"},
3131
Usage: "replaces existing data if the model supports it and the source priority is equal or higher",
3232
},
33+
DryRunFlag("preview the run without executing any models"),
3334
},
3435
Action: visionRunAction,
3536
}
@@ -45,10 +46,21 @@ func visionRunAction(ctx *cli.Context) error {
4546
return cli.Exit(err.Error(), 1)
4647
}
4748

49+
models := vision.ParseModelTypes(ctx.String("models"))
50+
51+
if ctx.Bool("dry-run") {
52+
modelList := strings.Join(models, ",")
53+
if modelList == "" {
54+
modelList = "(none)"
55+
}
56+
log.Infof("dry-run: vision run would execute models [%s] with filter=%q (count=%d, source=%s, force=%v)", modelList, filter, ctx.Int("count"), string(source), ctx.Bool("force"))
57+
return nil
58+
}
59+
4860
return worker.Start(
4961
filter,
5062
ctx.Int("count"),
51-
vision.ParseModelTypes(ctx.String("models")),
63+
models,
5264
string(source),
5365
ctx.Bool("force"),
5466
vision.RunManual,

0 commit comments

Comments
 (0)