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 }