mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-11 16:24:11 +01:00
156 lines
4.2 KiB
Go
156 lines
4.2 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/manifoldco/promptui"
|
|
"github.com/urfave/cli/v2"
|
|
|
|
"github.com/photoprism/photoprism/internal/auth/acl"
|
|
"github.com/photoprism/photoprism/internal/config"
|
|
"github.com/photoprism/photoprism/internal/event"
|
|
reg "github.com/photoprism/photoprism/internal/service/cluster/registry"
|
|
"github.com/photoprism/photoprism/pkg/clean"
|
|
"github.com/photoprism/photoprism/pkg/log/status"
|
|
)
|
|
|
|
// flags for nodes mod
|
|
var (
|
|
nodesModRoleFlag = &cli.StringFlag{Name: "role", Aliases: []string{"t"}, Usage: "node `ROLE` (portal, app, service)"}
|
|
nodesModInternal = &cli.StringFlag{Name: "advertise-url", Aliases: []string{"i"}, Usage: "internal service `URL`"}
|
|
nodesModLabel = &cli.StringSliceFlag{Name: "label", Aliases: []string{"l"}, Usage: "`k=v` label (repeatable)"}
|
|
)
|
|
|
|
// ClusterNodesModCommand updates node fields.
|
|
var ClusterNodesModCommand = &cli.Command{
|
|
Name: "mod",
|
|
Usage: "Updates node properties",
|
|
ArgsUsage: "<id|name>",
|
|
Flags: []cli.Flag{
|
|
DryRunFlag("preview updates without modifying the registry"),
|
|
nodesModRoleFlag,
|
|
nodesModInternal,
|
|
nodesModLabel,
|
|
YesFlag(),
|
|
},
|
|
Hidden: true, // Required for cluster-management only.
|
|
Action: clusterNodesModAction,
|
|
}
|
|
|
|
func clusterNodesModAction(ctx *cli.Context) error {
|
|
return CallWithDependencies(ctx, func(conf *config.Config) error {
|
|
if !conf.Portal() {
|
|
return cli.Exit(fmt.Errorf("node update is only available on a Portal node"), 2)
|
|
}
|
|
|
|
key := ctx.Args().First()
|
|
if key == "" {
|
|
return cli.Exit(fmt.Errorf("node id or name is required"), 2)
|
|
}
|
|
|
|
r, err := reg.NewClientRegistryWithConfig(conf)
|
|
if err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
|
|
// Resolve by NodeUUID first, then by client UID, then by normalized name.
|
|
var n *reg.Node
|
|
var getErr error
|
|
if n, getErr = r.FindByNodeUUID(key); getErr != nil || n == nil {
|
|
n, getErr = r.FindByClientID(key)
|
|
}
|
|
if getErr != nil || n == nil {
|
|
name := clean.DNSLabel(key)
|
|
if name == "" {
|
|
return cli.Exit(fmt.Errorf("invalid node identifier"), 2)
|
|
}
|
|
n, getErr = r.FindByName(name)
|
|
}
|
|
if getErr != nil || n == nil {
|
|
return cli.Exit(fmt.Errorf("node not found"), 3)
|
|
}
|
|
|
|
changes := make([]string, 0, 4)
|
|
|
|
if v := ctx.String("role"); v != "" {
|
|
n.Role = clean.TypeLowerDash(v)
|
|
changes = append(changes, fmt.Sprintf("role=%s", clean.Log(n.Role)))
|
|
}
|
|
if v := ctx.String("advertise-url"); v != "" {
|
|
n.AdvertiseUrl = v
|
|
changes = append(changes, fmt.Sprintf("advertise-url=%s", clean.Log(n.AdvertiseUrl)))
|
|
}
|
|
if labels := ctx.StringSlice("label"); len(labels) > 0 {
|
|
if n.Labels == nil {
|
|
n.Labels = map[string]string{}
|
|
}
|
|
for _, kv := range labels {
|
|
if k, v, ok := splitKV(kv); ok {
|
|
n.Labels[k] = v
|
|
}
|
|
}
|
|
changes = append(changes, fmt.Sprintf("labels+=%s", clean.Log(strings.Join(labels, ","))))
|
|
}
|
|
|
|
if ctx.Bool("dry-run") {
|
|
if len(changes) == 0 {
|
|
log.Infof("dry-run: no updates to apply for node %s", clean.LogQuote(n.Name))
|
|
} else {
|
|
log.Infof("dry-run: would update node %s (%s)", clean.LogQuote(n.Name), strings.Join(changes, ", "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
confirmed := RunNonInteractively(ctx.Bool("yes"))
|
|
if !confirmed {
|
|
prompt := promptui.Prompt{Label: fmt.Sprintf("Update node %s?", clean.LogQuote(n.Name)), IsConfirm: true}
|
|
if _, err := prompt.Run(); err != nil {
|
|
log.Infof("update cancelled for %s", clean.LogQuote(n.Name))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if err := r.Put(n); err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
|
|
nodeID := n.UUID
|
|
if nodeID == "" {
|
|
nodeID = n.Name
|
|
}
|
|
|
|
changeSummary := strings.Join(changes, ", ")
|
|
|
|
who := clusterAuditWho(ctx, conf)
|
|
segments := []string{
|
|
string(acl.ResourceCluster),
|
|
"update node", "%s",
|
|
}
|
|
args := []interface{}{clean.Log(nodeID)}
|
|
|
|
if changeSummary != "" {
|
|
segments = append(segments, "%s")
|
|
args = append(args, clean.Log(changeSummary))
|
|
}
|
|
|
|
segments = append(segments, status.Updated)
|
|
|
|
event.AuditInfo(append(who, segments...), args...)
|
|
|
|
log.Infof("node %s has been updated", clean.LogQuote(n.Name))
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func splitKV(s string) (string, string, bool) {
|
|
if s == "" {
|
|
return "", "", false
|
|
}
|
|
i := strings.IndexByte(s, '=')
|
|
if i <= 0 || i >= len(s)-1 {
|
|
return "", "", false
|
|
}
|
|
return s[:i], s[i+1:], true
|
|
}
|