code: func onListNodes(cf *CLIConf) {
  	tc, err := makeClient(cf, true)
  	if err != nil {
  		utils.FatalError(err)
  	}
  
  	// Get list of all nodes in backend and sort by "Node Name".
  	var nodes []services.Server
  	err = client.RetryWithRelogin(cf.Context, tc, func() error {
  		nodes, err = tc.ListNodes(cf.Context)
  		return err
  	})
  	if err != nil {
  		utils.FatalError(err)
  	}
  	sort.Slice(nodes, func(i, j int) bool {
  		return nodes[i].GetHostname() < nodes[j].GetHostname()
  	})
  
  	switch cf.Verbose {
  	// In verbose mode, print everything on a single line and include the Node
  	// ID (UUID). Useful for machines that need to parse the output of "tsh ls".
  	case true:
  		t := asciitable.MakeTable([]string{"Node Name", "Node ID", "Address", "Labels"})
  		for _, n := range nodes {
  			t.AddRow([]string{
  				n.GetHostname(), n.GetName(), n.GetAddr(), n.LabelsString(),
  			})
  		}
  		fmt.Println(t.AsBuffer().String())
  	// In normal mode chunk the labels and print two per line and allow multiple
  	// lines per node.
  	case false:
  		t := asciitable.MakeTable([]string{"Node Name", "Address", "Labels"})
  		for _, n := range nodes {
  			labelChunks := chunkLabels(n.GetAllLabels(), 2)
  			for i, v := range labelChunks {
  				var hostname string
  				var addr string
  				if i == 0 {
  					hostname = n.GetHostname()
  					addr = n.GetAddr()
  				}
  				t.AddRow([]string{hostname, addr, strings.Join(v, ", ")})
  			}
  		}
  		fmt.Println(t.AsBuffer().String())
  	}
  }
docs: // onListNodes executes 'tsh ls' command.