vitess.io/vitess@v0.16.2/go/cmd/zk/zkcmd.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreedto in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "archive/zip" 21 "bytes" 22 "context" 23 "fmt" 24 "io" 25 "os" 26 "os/exec" 27 "os/signal" 28 "path" 29 "sort" 30 "strings" 31 "sync" 32 "syscall" 33 "time" 34 35 "github.com/spf13/pflag" 36 "github.com/z-division/go-zookeeper/zk" 37 "golang.org/x/term" 38 39 "vitess.io/vitess/go/acl" 40 "vitess.io/vitess/go/exit" 41 "vitess.io/vitess/go/vt/log" 42 "vitess.io/vitess/go/vt/logutil" 43 "vitess.io/vitess/go/vt/topo" 44 "vitess.io/vitess/go/vt/topo/zk2topo" 45 ) 46 47 var doc = ` 48 zk is a tool for wrangling the zookeeper 49 50 It tries to mimic unix file system commands wherever possible, but 51 there are some slight differences in flag handling. 52 53 zk -h - provide help on overriding cell selection 54 55 zk addAuth digest user:pass 56 57 zk cat /zk/path 58 zk cat -l /zk/path1 /zk/path2 (list filename before file data) 59 60 zk chmod n-mode /zk/path 61 zk chmod n+mode /zk/path 62 63 zk cp /zk/path . 64 zk cp ./config /zk/path/config 65 zk cp ./config /zk/path/ (trailing slash indicates directory) 66 67 zk edit /zk/path (create a local copy, edit and write changes back to cell) 68 69 zk ls /zk 70 zk ls -l /zk 71 zk ls -ld /zk (list directory node itself) 72 zk ls -R /zk (recursive, expensive) 73 74 zk stat /zk/path 75 76 zk touch /zk/path 77 zk touch -c /zk/path (don't create, just touch timestamp) 78 zk touch -p /zk/path (create all parts necessary, think mkdir -p) 79 NOTE: there is no mkdir - just touch a node. The distinction 80 between file and directory is just not relevant in zookeeper. 81 82 zk rm /zk/path 83 zk rm -r /zk/path (recursive) 84 zk rm -f /zk/path (no error on nonexistent node) 85 86 zk wait /zk/path (wait for node change or creation) 87 zk wait /zk/path/children/ (trailing slash waits on children) 88 89 zk watch /zk/path (print changes) 90 91 zk unzip zktree.zip / 92 zk unzip zktree.zip /zk/prefix 93 94 zk zip /zk/root zktree.zip 95 NOTE: zip file can't be dumped to the file system since znodes 96 can have data and children. 97 98 The zk tool looks for the address of the cluster in /etc/zookeeper/zk_client.conf, 99 or the file specified in the ZK_CLIENT_CONFIG environment variable. 100 101 The local cell may be overridden with the ZK_CLIENT_LOCAL_CELL environment 102 variable. 103 ` 104 105 const ( 106 timeFmt = "2006-01-02 15:04:05" 107 timeFmtMicro = "2006-01-02 15:04:05.000000" 108 ) 109 110 type cmdFunc func(ctx context.Context, subFlags *pflag.FlagSet, args []string) error 111 112 var cmdMap map[string]cmdFunc 113 var zconn *zk2topo.ZkConn 114 var server string 115 116 func init() { 117 cmdMap = map[string]cmdFunc{ 118 "addAuth": cmdAddAuth, 119 "cat": cmdCat, 120 "chmod": cmdChmod, 121 "cp": cmdCp, 122 "edit": cmdEdit, 123 "ls": cmdLs, 124 "rm": cmdRm, 125 "stat": cmdStat, 126 "touch": cmdTouch, 127 "unzip": cmdUnzip, 128 "wait": cmdWait, 129 "watch": cmdWatch, 130 "zip": cmdZip, 131 } 132 } 133 134 func main() { 135 defer exit.Recover() 136 defer logutil.Flush() 137 pflag.StringVar(&server, "server", server, "server(s) to connect to") 138 // handling case of --help & -h 139 var help bool 140 pflag.BoolVarP(&help, "help", "h", false, "display usage and exit") 141 log.RegisterFlags(pflag.CommandLine) 142 logutil.RegisterFlags(pflag.CommandLine) 143 acl.RegisterFlags(pflag.CommandLine) 144 pflag.CommandLine.Usage = func() { 145 fmt.Fprint(os.Stderr, doc) 146 pflag.Usage() 147 } 148 149 pflag.Parse() 150 logutil.PurgeLogs() 151 152 if help || pflag.Arg(0) == "help" { 153 pflag.Usage() 154 os.Exit(0) 155 } 156 157 // if no zk command is provided after --server then we need to print doc & usage both 158 args := pflag.Args() 159 if len(args) == 0 { 160 pflag.CommandLine.Usage() 161 exit.Return(1) 162 } 163 cmdName := args[0] 164 args = args[1:] 165 cmd, ok := cmdMap[cmdName] 166 if !ok { 167 log.Exitf("Unknown command %v", cmdName) 168 } 169 subFlags := pflag.NewFlagSet(cmdName, pflag.ContinueOnError) 170 171 // Create a context for the command, cancel it if we get a signal. 172 ctx, cancel := context.WithCancel(context.Background()) 173 sigRecv := make(chan os.Signal, 1) 174 signal.Notify(sigRecv, os.Interrupt) 175 go func() { 176 <-sigRecv 177 cancel() 178 }() 179 180 // Connect to the server. 181 zconn = zk2topo.Connect(server) 182 183 // Run the command. 184 if err := cmd(ctx, subFlags, args); err != nil { 185 log.Error(err) 186 exit.Return(1) 187 } 188 } 189 190 func fixZkPath(zkPath string) string { 191 if zkPath != "/" { 192 zkPath = strings.TrimSuffix(zkPath, "/") 193 } 194 return path.Clean(zkPath) 195 } 196 197 func isZkFile(path string) bool { 198 return strings.HasPrefix(path, "/zk") 199 } 200 201 func cmdWait(ctx context.Context, subFlags *pflag.FlagSet, args []string) error { 202 var exitIfExists bool 203 subFlags.BoolVarP(&exitIfExists, "exit", "e", false, "exit if the path already exists") 204 205 if err := subFlags.Parse(args); err != nil { 206 return err 207 } 208 209 if subFlags.NArg() != 1 { 210 return fmt.Errorf("wait: can only wait for one path") 211 } 212 zkPath := subFlags.Arg(0) 213 isDir := zkPath[len(zkPath)-1] == '/' 214 zkPath = fixZkPath(zkPath) 215 216 var wait <-chan zk.Event 217 var err error 218 if isDir { 219 _, _, wait, err = zconn.ChildrenW(ctx, zkPath) 220 } else { 221 _, _, wait, err = zconn.GetW(ctx, zkPath) 222 } 223 if err != nil { 224 if err == zk.ErrNoNode { 225 _, _, wait, _ = zconn.ExistsW(ctx, zkPath) 226 } else { 227 return fmt.Errorf("wait: error %v: %v", zkPath, err) 228 } 229 } else { 230 if exitIfExists { 231 return fmt.Errorf("already exists: %v", zkPath) 232 } 233 } 234 event := <-wait 235 fmt.Printf("event: %v\n", event) 236 return nil 237 } 238 239 // Watch for changes to the node. 240 func cmdWatch(ctx context.Context, subFlags *pflag.FlagSet, args []string) error { 241 if err := subFlags.Parse(args); err != nil { 242 return err 243 } 244 245 eventChan := make(chan zk.Event, 16) 246 for _, arg := range subFlags.Args() { 247 zkPath := fixZkPath(arg) 248 _, _, watch, err := zconn.GetW(ctx, zkPath) 249 if err != nil { 250 return fmt.Errorf("watch error: %v", err) 251 } 252 go func() { 253 eventChan <- <-watch 254 }() 255 } 256 257 for { 258 select { 259 case <-ctx.Done(): 260 return nil 261 case event := <-eventChan: 262 log.Infof("watch: event %v: %v", event.Path, event) 263 if event.Type == zk.EventNodeDataChanged { 264 data, stat, watch, err := zconn.GetW(ctx, event.Path) 265 if err != nil { 266 return fmt.Errorf("ERROR: failed to watch %v", err) 267 } 268 log.Infof("watch: %v %v\n", event.Path, stat) 269 println(data) 270 go func() { 271 eventChan <- <-watch 272 }() 273 } else if event.State == zk.StateDisconnected { 274 return nil 275 } else if event.Type == zk.EventNodeDeleted { 276 log.Infof("watch: %v deleted\n", event.Path) 277 } else { 278 // Most likely a session event - try t 279 _, _, watch, err := zconn.GetW(ctx, event.Path) 280 if err != nil { 281 return fmt.Errorf("ERROR: failed to watch %v", err) 282 } 283 go func() { 284 eventChan <- <-watch 285 }() 286 } 287 } 288 } 289 } 290 291 func cmdLs(ctx context.Context, subFlags *pflag.FlagSet, args []string) error { 292 var ( 293 longListing bool 294 directoryListing bool 295 force bool 296 recursiveListing bool 297 ) 298 subFlags.BoolVarP(&longListing, "longlisting", "l", false, "long listing") 299 subFlags.BoolVarP(&directoryListing, "directorylisting", "d", false, "list directory instead of contents") 300 subFlags.BoolVarP(&force, "force", "f", false, "no warning on nonexistent node") 301 subFlags.BoolVarP(&recursiveListing, "recursivelisting", "R", false, "recursive listing") 302 303 if err := subFlags.Parse(args); err != nil { 304 return err 305 } 306 if subFlags.NArg() == 0 { 307 return fmt.Errorf("ls: no path specified") 308 } 309 resolved, err := zk2topo.ResolveWildcards(ctx, zconn, subFlags.Args()) 310 if err != nil { 311 return fmt.Errorf("ls: invalid wildcards: %v", err) 312 } 313 if len(resolved) == 0 { 314 // the wildcards didn't result in anything, we're 315 // done. 316 return nil 317 } 318 319 hasError := false 320 needsHeader := len(resolved) > 1 && !directoryListing 321 for _, arg := range resolved { 322 zkPath := fixZkPath(arg) 323 var children []string 324 var err error 325 isDir := true 326 if directoryListing { 327 children = []string{""} 328 isDir = false 329 } else if recursiveListing { 330 children, err = zk2topo.ChildrenRecursive(ctx, zconn, zkPath) 331 } else { 332 children, _, err = zconn.Children(ctx, zkPath) 333 // Assume this is a file node if it has no children. 334 if len(children) == 0 { 335 children = []string{""} 336 isDir = false 337 } 338 } 339 if err != nil { 340 hasError = true 341 if !force || err != zk.ErrNoNode { 342 log.Warningf("ls: cannot access %v: %v", zkPath, err) 343 } 344 } 345 346 // Show the full path when it helps. 347 showFullPath := false 348 if recursiveListing { 349 showFullPath = true 350 } else if longListing && (directoryListing || !isDir) { 351 showFullPath = true 352 } 353 if needsHeader { 354 fmt.Printf("%v:\n", zkPath) 355 } 356 if len(children) > 0 { 357 if longListing && isDir { 358 fmt.Printf("total: %v\n", len(children)) 359 } 360 sort.Strings(children) 361 stats := make([]*zk.Stat, len(children)) 362 wg := sync.WaitGroup{} 363 f := func(i int) { 364 localPath := path.Join(zkPath, children[i]) 365 _, stat, err := zconn.Exists(ctx, localPath) 366 if err != nil { 367 if !force || err != zk.ErrNoNode { 368 log.Warningf("ls: cannot access: %v: %v", localPath, err) 369 } 370 } else { 371 stats[i] = stat 372 } 373 wg.Done() 374 } 375 for i := range children { 376 wg.Add(1) 377 go f(i) 378 } 379 wg.Wait() 380 381 for i, child := range children { 382 localPath := path.Join(zkPath, child) 383 if stat := stats[i]; stat != nil { 384 fmtPath(stat, localPath, showFullPath, longListing) 385 } 386 } 387 } 388 if needsHeader { 389 fmt.Println() 390 } 391 } 392 if hasError { 393 return fmt.Errorf("ls: some paths had errors") 394 } 395 return nil 396 } 397 398 func fmtPath(stat *zk.Stat, zkPath string, showFullPath bool, longListing bool) { 399 var name, perms string 400 401 if !showFullPath { 402 name = path.Base(zkPath) 403 } else { 404 name = zkPath 405 } 406 407 if longListing { 408 if stat.NumChildren > 0 { 409 // FIXME(msolomon) do permissions check? 410 perms = "drwxrwxrwx" 411 if stat.DataLength > 0 { 412 // give a visual indication that this node has data as well as children 413 perms = "nrw-rw-rw-" 414 } 415 } else if stat.EphemeralOwner != 0 { 416 perms = "erw-rw-rw-" 417 } else { 418 perms = "-rw-rw-rw-" 419 } 420 // always print the Local version of the time. zookeeper's 421 // go / C library would return a local time anyway, but 422 // might as well be sure. 423 fmt.Printf("%v %v %v % 8v % 20v %v\n", perms, "zk", "zk", stat.DataLength, zk2topo.Time(stat.Mtime).Local().Format(timeFmt), name) 424 } else { 425 fmt.Printf("%v\n", name) 426 } 427 } 428 429 func cmdTouch(ctx context.Context, subFlags *pflag.FlagSet, args []string) error { 430 var ( 431 createParents bool 432 touchOnly bool 433 ) 434 435 subFlags.BoolVarP(&createParents, "createparent", "p", false, "create parents") 436 subFlags.BoolVarP(&touchOnly, "touchonly", "c", false, "touch only - don't create") 437 438 if err := subFlags.Parse(args); err != nil { 439 return err 440 } 441 if subFlags.NArg() != 1 { 442 return fmt.Errorf("touch: need to specify exactly one path") 443 } 444 445 zkPath := fixZkPath(subFlags.Arg(0)) 446 447 var ( 448 version int32 = -1 449 create = false 450 ) 451 452 data, stat, err := zconn.Get(ctx, zkPath) 453 switch { 454 case err == nil: 455 version = stat.Version 456 case err == zk.ErrNoNode: 457 create = true 458 default: 459 return fmt.Errorf("touch: cannot access %v: %v", zkPath, err) 460 } 461 462 switch { 463 case !create: 464 _, err = zconn.Set(ctx, zkPath, data, version) 465 case touchOnly: 466 return fmt.Errorf("touch: no such path %v", zkPath) 467 case createParents: 468 _, err = zk2topo.CreateRecursive(ctx, zconn, zkPath, data, 0, zk.WorldACL(zk.PermAll), 10) 469 default: 470 _, err = zconn.Create(ctx, zkPath, data, 0, zk.WorldACL(zk.PermAll)) 471 } 472 473 if err != nil { 474 return fmt.Errorf("touch: cannot modify %v: %v", zkPath, err) 475 } 476 return nil 477 } 478 479 func cmdRm(ctx context.Context, subFlags *pflag.FlagSet, args []string) error { 480 var ( 481 force bool 482 recursiveDelete bool 483 forceAndRecursive bool 484 ) 485 subFlags.BoolVarP(&force, "force", "f", false, "no warning on nonexistent node") 486 subFlags.BoolVarP(&recursiveDelete, "recursivedelete", "r", false, "recursive delete") 487 subFlags.BoolVarP(&forceAndRecursive, "forceandrecursive", "rf", false, "shorthand for -r -f") 488 489 if err := subFlags.Parse(args); err != nil { 490 return err 491 } 492 force = force || forceAndRecursive 493 recursiveDelete = recursiveDelete || forceAndRecursive 494 495 if subFlags.NArg() == 0 { 496 return fmt.Errorf("rm: no path specified") 497 } 498 499 if recursiveDelete { 500 for _, arg := range subFlags.Args() { 501 zkPath := fixZkPath(arg) 502 if strings.Count(zkPath, "/") < 2 { 503 return fmt.Errorf("rm: overly general path: %v", zkPath) 504 } 505 } 506 } 507 508 resolved, err := zk2topo.ResolveWildcards(ctx, zconn, subFlags.Args()) 509 if err != nil { 510 return fmt.Errorf("rm: invalid wildcards: %v", err) 511 } 512 if len(resolved) == 0 { 513 // the wildcards didn't result in anything, we're done 514 return nil 515 } 516 517 hasError := false 518 for _, arg := range resolved { 519 zkPath := fixZkPath(arg) 520 var err error 521 if recursiveDelete { 522 err = zk2topo.DeleteRecursive(ctx, zconn, zkPath, -1) 523 } else { 524 err = zconn.Delete(ctx, zkPath, -1) 525 } 526 if err != nil && (!force || err != zk.ErrNoNode) { 527 hasError = true 528 log.Warningf("rm: cannot delete %v: %v", zkPath, err) 529 } 530 } 531 if hasError { 532 // to be consistent with the command line 'rm -f', return 533 // 0 if using 'zk rm -f' and the file doesn't exist. 534 return fmt.Errorf("rm: some paths had errors") 535 } 536 return nil 537 } 538 539 func cmdAddAuth(ctx context.Context, subFlags *pflag.FlagSet, args []string) error { 540 if err := subFlags.Parse(args); err != nil { 541 return err 542 } 543 if subFlags.NArg() < 2 { 544 return fmt.Errorf("addAuth: expected args <scheme> <auth>") 545 } 546 scheme, auth := subFlags.Arg(0), subFlags.Arg(1) 547 return zconn.AddAuth(ctx, scheme, []byte(auth)) 548 } 549 550 func cmdCat(ctx context.Context, subFlags *pflag.FlagSet, args []string) error { 551 var ( 552 longListing bool 553 force bool 554 decodeProto bool 555 ) 556 subFlags.BoolVarP(&longListing, "longListing", "l", false, "long listing") 557 subFlags.BoolVarP(&force, "force", "f", false, "no warning on nonexistent node") 558 subFlags.BoolVarP(&decodeProto, "decodeProto", "p", false, "decode proto files and display them as text") 559 560 if err := subFlags.Parse(args); err != nil { 561 return err 562 } 563 if subFlags.NArg() == 0 { 564 return fmt.Errorf("cat: no path specified") 565 } 566 resolved, err := zk2topo.ResolveWildcards(ctx, zconn, subFlags.Args()) 567 if err != nil { 568 return fmt.Errorf("cat: invalid wildcards: %v", err) 569 } 570 if len(resolved) == 0 { 571 // the wildcards didn't result in anything, we're done 572 return nil 573 } 574 575 hasError := false 576 for _, arg := range resolved { 577 zkPath := fixZkPath(arg) 578 data, _, err := zconn.Get(ctx, zkPath) 579 if err != nil { 580 hasError = true 581 if !force || err != zk.ErrNoNode { 582 log.Warningf("cat: cannot access %v: %v", zkPath, err) 583 } 584 continue 585 } 586 587 if longListing { 588 fmt.Printf("%v:\n", zkPath) 589 } 590 decoded := "" 591 if decodeProto { 592 decoded, err = topo.DecodeContent(zkPath, data, false) 593 if err != nil { 594 log.Warningf("cat: cannot proto decode %v: %v", zkPath, err) 595 decoded = string(data) 596 } 597 } else { 598 decoded = string(data) 599 } 600 fmt.Print(decoded) 601 if len(decoded) > 0 && decoded[len(decoded)-1] != '\n' && (term.IsTerminal(int(os.Stdout.Fd())) || longListing) { 602 fmt.Print("\n") 603 } 604 } 605 if hasError { 606 return fmt.Errorf("cat: some paths had errors") 607 } 608 return nil 609 } 610 611 func cmdEdit(ctx context.Context, subFlags *pflag.FlagSet, args []string) error { 612 var force bool 613 subFlags.BoolVarP(&force, "force", "f", false, "no warning on nonexistent node") 614 615 if err := subFlags.Parse(args); err != nil { 616 return err 617 } 618 if subFlags.NArg() == 0 { 619 return fmt.Errorf("edit: no path specified") 620 } 621 arg := subFlags.Arg(0) 622 zkPath := fixZkPath(arg) 623 data, stat, err := zconn.Get(ctx, zkPath) 624 if err != nil { 625 if !force || err != zk.ErrNoNode { 626 log.Warningf("edit: cannot access %v: %v", zkPath, err) 627 } 628 return fmt.Errorf("edit: cannot access %v: %v", zkPath, err) 629 } 630 631 name := path.Base(zkPath) 632 tmpPath := fmt.Sprintf("/tmp/zk-edit-%v-%v", name, time.Now().UnixNano()) 633 f, err := os.Create(tmpPath) 634 if err == nil { 635 _, err = f.Write(data) 636 f.Close() 637 } 638 if err != nil { 639 return fmt.Errorf("edit: cannot write file %v", err) 640 } 641 642 cmd := exec.Command(os.Getenv("EDITOR"), tmpPath) 643 cmd.Stdin = os.Stdin 644 cmd.Stdout = os.Stdout 645 cmd.Stderr = os.Stderr 646 err = cmd.Run() 647 if err != nil { 648 os.Remove(tmpPath) 649 return fmt.Errorf("edit: cannot start $EDITOR: %v", err) 650 } 651 652 fileData, err := os.ReadFile(tmpPath) 653 if err != nil { 654 os.Remove(tmpPath) 655 return fmt.Errorf("edit: cannot read file %v", err) 656 } 657 658 if !bytes.Equal(fileData, data) { 659 // data changed - update if we can 660 _, err = zconn.Set(ctx, zkPath, fileData, stat.Version) 661 if err != nil { 662 os.Remove(tmpPath) 663 return fmt.Errorf("edit: cannot write zk file %v", err) 664 } 665 } 666 os.Remove(tmpPath) 667 return nil 668 } 669 670 func cmdStat(ctx context.Context, subFlags *pflag.FlagSet, args []string) error { 671 var force bool 672 subFlags.BoolVarP(&force, "force", "f", false, "no warning on nonexistent node") 673 674 if err := subFlags.Parse(args); err != nil { 675 return err 676 } 677 678 if subFlags.NArg() == 0 { 679 return fmt.Errorf("stat: no path specified") 680 } 681 682 resolved, err := zk2topo.ResolveWildcards(ctx, zconn, subFlags.Args()) 683 if err != nil { 684 return fmt.Errorf("stat: invalid wildcards: %v", err) 685 } 686 if len(resolved) == 0 { 687 // the wildcards didn't result in anything, we're done 688 return nil 689 } 690 691 hasError := false 692 for _, arg := range resolved { 693 zkPath := fixZkPath(arg) 694 acls, stat, err := zconn.GetACL(ctx, zkPath) 695 if stat == nil { 696 err = fmt.Errorf("no such node") 697 } 698 if err != nil { 699 hasError = true 700 if !force || err != zk.ErrNoNode { 701 log.Warningf("stat: cannot access %v: %v", zkPath, err) 702 } 703 continue 704 } 705 fmt.Printf("Path: %s\n", zkPath) 706 fmt.Printf("Created: %s\n", zk2topo.Time(stat.Ctime).Format(timeFmtMicro)) 707 fmt.Printf("Modified: %s\n", zk2topo.Time(stat.Mtime).Format(timeFmtMicro)) 708 fmt.Printf("Size: %v\n", stat.DataLength) 709 fmt.Printf("Children: %v\n", stat.NumChildren) 710 fmt.Printf("Version: %v\n", stat.Version) 711 fmt.Printf("Ephemeral: %v\n", stat.EphemeralOwner) 712 fmt.Printf("ACL:\n") 713 for _, acl := range acls { 714 fmt.Printf(" %v:%v %v\n", acl.Scheme, acl.ID, fmtACL(acl)) 715 } 716 } 717 if hasError { 718 return fmt.Errorf("stat: some paths had errors") 719 } 720 return nil 721 } 722 723 var charPermMap map[string]int32 724 var permCharMap map[int32]string 725 726 func init() { 727 charPermMap = map[string]int32{ 728 "r": zk.PermRead, 729 "w": zk.PermWrite, 730 "d": zk.PermDelete, 731 "c": zk.PermCreate, 732 "a": zk.PermAdmin, 733 } 734 permCharMap = make(map[int32]string) 735 for c, p := range charPermMap { 736 permCharMap[p] = c 737 } 738 } 739 740 func fmtACL(acl zk.ACL) string { 741 s := "" 742 743 for _, perm := range []int32{zk.PermRead, zk.PermWrite, zk.PermDelete, zk.PermCreate, zk.PermAdmin} { 744 if acl.Perms&perm != 0 { 745 s += permCharMap[perm] 746 } else { 747 s += "-" 748 } 749 } 750 return s 751 } 752 753 func cmdChmod(ctx context.Context, subFlags *pflag.FlagSet, args []string) error { 754 if err := subFlags.Parse(args); err != nil { 755 return err 756 } 757 if subFlags.NArg() < 2 { 758 return fmt.Errorf("chmod: no permission specified") 759 } 760 mode := subFlags.Arg(0) 761 if mode[0] != 'n' { 762 return fmt.Errorf("chmod: invalid mode") 763 } 764 765 addPerms := false 766 if mode[1] == '+' { 767 addPerms = true 768 } else if mode[1] != '-' { 769 return fmt.Errorf("chmod: invalid mode") 770 } 771 772 var permMask int32 773 for _, c := range mode[2:] { 774 permMask |= charPermMap[string(c)] 775 } 776 777 resolved, err := zk2topo.ResolveWildcards(ctx, zconn, subFlags.Args()[1:]) 778 if err != nil { 779 return fmt.Errorf("chmod: invalid wildcards: %v", err) 780 } 781 if len(resolved) == 0 { 782 // the wildcards didn't result in anything, we're done 783 return nil 784 } 785 786 hasError := false 787 for _, arg := range resolved { 788 zkPath := fixZkPath(arg) 789 aclv, _, err := zconn.GetACL(ctx, zkPath) 790 if err != nil { 791 hasError = true 792 log.Warningf("chmod: cannot set access %v: %v", zkPath, err) 793 continue 794 } 795 if addPerms { 796 aclv[0].Perms |= permMask 797 } else { 798 aclv[0].Perms &= ^permMask 799 } 800 err = zconn.SetACL(ctx, zkPath, aclv, -1) 801 if err != nil { 802 hasError = true 803 log.Warningf("chmod: cannot set access %v: %v", zkPath, err) 804 continue 805 } 806 } 807 if hasError { 808 return fmt.Errorf("chmod: some paths had errors") 809 } 810 return nil 811 } 812 813 func cmdCp(ctx context.Context, subFlags *pflag.FlagSet, args []string) error { 814 if err := subFlags.Parse(args); err != nil { 815 return err 816 } 817 switch { 818 case subFlags.NArg() < 2: 819 return fmt.Errorf("cp: need to specify source and destination paths") 820 case subFlags.NArg() == 2: 821 return fileCp(ctx, args[0], args[1]) 822 default: 823 return multiFileCp(ctx, args) 824 } 825 } 826 827 func getPathData(ctx context.Context, filePath string) ([]byte, error) { 828 if isZkFile(filePath) { 829 data, _, err := zconn.Get(ctx, filePath) 830 return data, err 831 } 832 var err error 833 file, err := os.Open(filePath) 834 if err == nil { 835 data, err := io.ReadAll(file) 836 if err == nil { 837 return data, err 838 } 839 } 840 return nil, err 841 } 842 843 func setPathData(ctx context.Context, filePath string, data []byte) error { 844 if isZkFile(filePath) { 845 _, err := zconn.Set(ctx, filePath, data, -1) 846 if err == zk.ErrNoNode { 847 _, err = zk2topo.CreateRecursive(ctx, zconn, filePath, data, 0, zk.WorldACL(zk.PermAll), 10) 848 } 849 return err 850 } 851 return os.WriteFile(filePath, []byte(data), 0666) 852 } 853 854 func fileCp(ctx context.Context, srcPath, dstPath string) error { 855 dstIsDir := dstPath[len(dstPath)-1] == '/' 856 srcPath = fixZkPath(srcPath) 857 dstPath = fixZkPath(dstPath) 858 859 if !isZkFile(srcPath) && !isZkFile(dstPath) { 860 return fmt.Errorf("cp: neither src nor dst is a /zk file: exitting") 861 } 862 863 data, err := getPathData(ctx, srcPath) 864 if err != nil { 865 return fmt.Errorf("cp: cannot read %v: %v", srcPath, err) 866 } 867 868 // If we are copying to a local directory - say '.', make the filename 869 // the same as the source. 870 if !isZkFile(dstPath) { 871 fileInfo, err := os.Stat(dstPath) 872 if err != nil { 873 if err.(*os.PathError).Err != syscall.ENOENT { 874 return fmt.Errorf("cp: cannot stat %v: %v", dstPath, err) 875 } 876 } else if fileInfo.IsDir() { 877 dstPath = path.Join(dstPath, path.Base(srcPath)) 878 } 879 } else if dstIsDir { 880 // If we are copying into zk, interpret trailing slash as treating the 881 // dstPath as a directory. 882 dstPath = path.Join(dstPath, path.Base(srcPath)) 883 } 884 if err := setPathData(ctx, dstPath, data); err != nil { 885 return fmt.Errorf("cp: cannot write %v: %v", dstPath, err) 886 } 887 return nil 888 } 889 890 func multiFileCp(ctx context.Context, args []string) error { 891 dstPath := args[len(args)-1] 892 if dstPath[len(dstPath)-1] != '/' { 893 // In multifile context, dstPath must be a directory. 894 dstPath += "/" 895 } 896 897 for _, srcPath := range args[:len(args)-1] { 898 if err := fileCp(ctx, srcPath, dstPath); err != nil { 899 return err 900 } 901 } 902 return nil 903 } 904 905 type zkItem struct { 906 path string 907 data []byte 908 stat *zk.Stat 909 err error 910 } 911 912 // Store a zk tree in a zip archive. This won't be immediately useful to 913 // zip tools since even "directories" can contain data. 914 func cmdZip(ctx context.Context, subFlags *pflag.FlagSet, args []string) error { 915 if err := subFlags.Parse(args); err != nil { 916 return err 917 } 918 if subFlags.NArg() < 2 { 919 return fmt.Errorf("zip: need to specify source and destination paths") 920 } 921 922 dstPath := subFlags.Arg(subFlags.NArg() - 1) 923 paths := subFlags.Args()[:len(args)-1] 924 if !strings.HasSuffix(dstPath, ".zip") { 925 return fmt.Errorf("zip: need to specify destination .zip path: %v", dstPath) 926 } 927 zipFile, err := os.Create(dstPath) 928 if err != nil { 929 return fmt.Errorf("zip: error %v", err) 930 } 931 932 wg := sync.WaitGroup{} 933 items := make(chan *zkItem, 64) 934 for _, arg := range paths { 935 zkPath := fixZkPath(arg) 936 children, err := zk2topo.ChildrenRecursive(ctx, zconn, zkPath) 937 if err != nil { 938 return fmt.Errorf("zip: error %v", err) 939 } 940 for _, child := range children { 941 toAdd := path.Join(zkPath, child) 942 wg.Add(1) 943 go func() { 944 data, stat, err := zconn.Get(ctx, toAdd) 945 items <- &zkItem{toAdd, data, stat, err} 946 wg.Done() 947 }() 948 } 949 } 950 go func() { 951 wg.Wait() 952 close(items) 953 }() 954 955 zipWriter := zip.NewWriter(zipFile) 956 for item := range items { 957 path, data, stat, err := item.path, item.data, item.stat, item.err 958 if err != nil { 959 return fmt.Errorf("zip: get failed: %v", err) 960 } 961 // Skip ephemerals - not sure why you would archive them. 962 if stat.EphemeralOwner > 0 { 963 continue 964 } 965 fi := &zip.FileHeader{Name: path, Method: zip.Deflate} 966 fi.Modified = zk2topo.Time(stat.Mtime) 967 f, err := zipWriter.CreateHeader(fi) 968 if err != nil { 969 return fmt.Errorf("zip: create failed: %v", err) 970 } 971 _, err = f.Write(data) 972 if err != nil { 973 return fmt.Errorf("zip: create failed: %v", err) 974 } 975 } 976 err = zipWriter.Close() 977 if err != nil { 978 return fmt.Errorf("zip: close failed: %v", err) 979 } 980 zipFile.Close() 981 return nil 982 } 983 984 func cmdUnzip(ctx context.Context, subFlags *pflag.FlagSet, args []string) error { 985 if err := subFlags.Parse(args); err != nil { 986 return err 987 } 988 if subFlags.NArg() != 2 { 989 return fmt.Errorf("zip: need to specify source and destination paths") 990 } 991 992 srcPath, dstPath := subFlags.Arg(0), subFlags.Arg(1) 993 994 if !strings.HasSuffix(srcPath, ".zip") { 995 return fmt.Errorf("zip: need to specify src .zip path: %v", srcPath) 996 } 997 998 zipReader, err := zip.OpenReader(srcPath) 999 if err != nil { 1000 return fmt.Errorf("zip: error %v", err) 1001 } 1002 defer zipReader.Close() 1003 1004 for _, zf := range zipReader.File { 1005 rc, err := zf.Open() 1006 if err != nil { 1007 return fmt.Errorf("unzip: error %v", err) 1008 } 1009 data, err := io.ReadAll(rc) 1010 if err != nil { 1011 return fmt.Errorf("unzip: failed reading archive: %v", err) 1012 } 1013 zkPath := zf.Name 1014 if dstPath != "/" { 1015 zkPath = path.Join(dstPath, zkPath) 1016 } 1017 _, err = zk2topo.CreateRecursive(ctx, zconn, zkPath, data, 0, zk.WorldACL(zk.PermAll), 10) 1018 if err != nil && err != zk.ErrNodeExists { 1019 return fmt.Errorf("unzip: zk create failed: %v", err) 1020 } 1021 _, err = zconn.Set(ctx, zkPath, data, -1) 1022 if err != nil { 1023 return fmt.Errorf("unzip: zk set failed: %v", err) 1024 } 1025 rc.Close() 1026 } 1027 return nil 1028 }