github.com/vmware/govmomi@v0.51.0/cli/object/tree.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package object
     6  
     7  import (
     8  	"context"
     9  	"flag"
    10  	"fmt"
    11  	"net/url"
    12  	"os"
    13  	gopath "path"
    14  	"time"
    15  
    16  	gotree "github.com/a8m/tree"
    17  
    18  	"github.com/vmware/govmomi/cli"
    19  	"github.com/vmware/govmomi/cli/flags"
    20  	"github.com/vmware/govmomi/view"
    21  	"github.com/vmware/govmomi/vim25"
    22  	"github.com/vmware/govmomi/vim25/types"
    23  )
    24  
    25  type tree struct {
    26  	*flags.DatacenterFlag
    27  
    28  	long  bool
    29  	kind  bool
    30  	color bool
    31  	level int
    32  }
    33  
    34  func init() {
    35  	cli.Register("tree", &tree{})
    36  }
    37  
    38  func (cmd *tree) Register(ctx context.Context, f *flag.FlagSet) {
    39  	cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx)
    40  	cmd.DatacenterFlag.Register(ctx, f)
    41  
    42  	f.BoolVar(&cmd.color, "C", false, "Colorize output")
    43  	f.BoolVar(&cmd.long, "l", false, "Follow runtime references (e.g. HostSystem VMs)")
    44  	f.BoolVar(&cmd.kind, "p", false, "Print the object type")
    45  	f.IntVar(&cmd.level, "L", 0, "Max display depth of the inventory tree")
    46  }
    47  
    48  func (cmd *tree) Description() string {
    49  	return `List contents of the inventory in a tree-like format.
    50  
    51  Examples:
    52    govc tree -C /
    53    govc tree /datacenter/vm`
    54  }
    55  
    56  func (cmd *tree) Usage() string {
    57  	return "[PATH]"
    58  }
    59  
    60  func (cmd *tree) Run(ctx context.Context, f *flag.FlagSet) error {
    61  	c, err := cmd.Client()
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	path := f.Arg(0)
    67  	if path == "" {
    68  		path = "/"
    69  	}
    70  
    71  	vfs := &virtualFileSystem{
    72  		ctx:   ctx,
    73  		cmd:   cmd,
    74  		c:     c,
    75  		m:     view.NewManager(c),
    76  		names: make(map[types.ManagedObjectReference]string),
    77  		dvs:   make(map[types.ManagedObjectReference][]types.ManagedObjectReference),
    78  		path:  path,
    79  	}
    80  
    81  	treeOpts := &gotree.Options{
    82  		Fs:        vfs,
    83  		OutFile:   cmd.Out,
    84  		Colorize:  cmd.color,
    85  		Color:     color,
    86  		DeepLevel: cmd.level,
    87  	}
    88  
    89  	inf := gotree.New(path)
    90  	inf.Visit(treeOpts)
    91  	inf.Print(treeOpts)
    92  
    93  	return nil
    94  }
    95  
    96  type virtualFileSystem struct {
    97  	ctx   context.Context
    98  	cmd   *tree
    99  	c     *vim25.Client
   100  	m     *view.Manager
   101  	names map[types.ManagedObjectReference]string
   102  	dvs   map[types.ManagedObjectReference][]types.ManagedObjectReference
   103  	root  types.ManagedObjectReference
   104  	path  string
   105  }
   106  
   107  func style(kind string) string {
   108  	switch kind {
   109  	case "VirtualMachine":
   110  		return "1;32"
   111  	case "HostSystem":
   112  		return "1;33"
   113  	case "ResourcePool":
   114  		return "1;30"
   115  	case "Network", "OpaqueNetwork", "DistributedVirtualPortgroup":
   116  		return "1;35"
   117  	case "Datastore":
   118  		return "1;36"
   119  	case "Datacenter":
   120  		return "1;37"
   121  	default:
   122  		return ""
   123  	}
   124  }
   125  
   126  func color(node *gotree.Node, s string) string {
   127  	ref := pathReference(node.Path())
   128  
   129  	switch ref.Type {
   130  	case "ResourcePool":
   131  		return s
   132  	}
   133  
   134  	c := style(ref.Type)
   135  	if c == "" {
   136  		return gotree.ANSIColor(node, s)
   137  	}
   138  
   139  	return gotree.ANSIColorFormat(c, s)
   140  }
   141  
   142  func (vfs *virtualFileSystem) Stat(path string) (os.FileInfo, error) {
   143  	var ref types.ManagedObjectReference
   144  
   145  	if len(vfs.names) == 0 {
   146  		// This is the first Stat() call, where path is the initial user input
   147  		if path == "/" {
   148  			ref = vfs.c.ServiceContent.RootFolder
   149  		} else {
   150  			var err error
   151  			ref, err = vfs.cmd.ManagedObject(vfs.ctx, path)
   152  			if err != nil {
   153  				return nil, err
   154  			}
   155  		}
   156  		vfs.names[ref] = path
   157  		vfs.root = ref
   158  	} else {
   159  		// The Node.Path in subsequent calls to Stat() will have a MOR base
   160  		ref = pathReference(path)
   161  	}
   162  
   163  	name := vfs.names[ref]
   164  
   165  	var mode os.FileMode
   166  	switch ref.Type {
   167  	case "ComputeResource",
   168  		"ClusterComputeResource",
   169  		"Datacenter",
   170  		"Folder",
   171  		"ResourcePool",
   172  		"VirtualApp",
   173  		"StoragePod",
   174  		"DistributedVirtualSwitch",
   175  		"VmwareDistributedVirtualSwitch":
   176  		mode = os.ModeDir
   177  	case "HostSystem":
   178  		if vfs.cmd.long {
   179  			mode = os.ModeDir
   180  		}
   181  	}
   182  
   183  	if vfs.cmd.kind {
   184  		name = fmt.Sprintf("[%s] %s", ref.Type, name)
   185  	}
   186  
   187  	return fileInfo{name: name, mode: mode}, nil
   188  }
   189  
   190  // pathReference converts the base of the given Node.Path to a MOR
   191  func pathReference(s string) types.ManagedObjectReference {
   192  	var ref types.ManagedObjectReference
   193  	r, _ := url.PathUnescape(gopath.Base(s))
   194  	ref.FromString(r)
   195  	return ref
   196  }
   197  
   198  func (vfs *virtualFileSystem) ReadDir(path string) ([]string, error) {
   199  	var ref types.ManagedObjectReference
   200  
   201  	if path == vfs.path {
   202  		// This path is the initial user input (e.g. "/" or "/dc1")
   203  		ref = vfs.root
   204  	} else {
   205  		// This path will have had 1 or more MORs appended to it, as returned by this func
   206  		ref = pathReference(path)
   207  	}
   208  
   209  	var childPaths []string
   210  
   211  	switch ref.Type {
   212  	// In the vCenter inventory switches and portgroups are siblings, hack to display them as parent child in the tree
   213  	case "DistributedVirtualSwitch", "VmwareDistributedVirtualSwitch":
   214  		pgs := vfs.dvs[ref]
   215  		for _, pg := range pgs {
   216  			childPaths = append(childPaths, url.PathEscape(pg.String()))
   217  		}
   218  		return childPaths, nil
   219  	}
   220  
   221  	v, err := vfs.m.CreateContainerView(vfs.ctx, ref, nil, false)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	defer v.Destroy(vfs.ctx)
   226  
   227  	var kind []string
   228  	if !vfs.cmd.long {
   229  		switch ref.Type {
   230  		case "HostSystem":
   231  			return nil, nil
   232  		case "ResourcePool", "VirtualApp":
   233  			kind = []string{"ResourcePool", "VirtualApp"}
   234  		}
   235  	}
   236  
   237  	var children []types.ObjectContent
   238  
   239  	pspec := []types.PropertySpec{
   240  		{Type: "DistributedVirtualSwitch", PathSet: []string{"portgroup"}},
   241  		{Type: "VmwareDistributedVirtualSwitch", PathSet: []string{"portgroup"}},
   242  	}
   243  
   244  	err = v.Retrieve(vfs.ctx, kind, []string{"name"}, &children, pspec...)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  
   249  	for _, content := range children {
   250  		ref = content.Obj
   251  		for _, p := range content.PropSet {
   252  			switch p.Name {
   253  			case "name":
   254  				vfs.names[ref] = p.Val.(string)
   255  			case "portgroup":
   256  				vfs.dvs[ref] = p.Val.(types.ArrayOfManagedObjectReference).ManagedObjectReference
   257  			}
   258  		}
   259  		if ref.Type == "DistributedVirtualPortgroup" {
   260  			continue // Returned on ReadDir() of the DVS above
   261  		}
   262  		childPaths = append(childPaths, url.PathEscape(ref.String()))
   263  	}
   264  
   265  	return childPaths, nil
   266  }
   267  
   268  type fileInfo struct {
   269  	name string
   270  	mode os.FileMode
   271  }
   272  
   273  func (f fileInfo) Name() string {
   274  	return f.name
   275  }
   276  func (f fileInfo) Size() int64 {
   277  	return 0
   278  }
   279  func (f fileInfo) Mode() os.FileMode {
   280  	return f.mode
   281  }
   282  func (f fileInfo) ModTime() time.Time {
   283  	return time.Now()
   284  }
   285  func (f fileInfo) IsDir() bool {
   286  	return f.mode&os.ModeDir == os.ModeDir
   287  }
   288  func (f fileInfo) Sys() any {
   289  	return nil
   290  }