From 8f3b0a38e653f24086eeaccf9694a15057c4f47a Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Sun, 3 Nov 2024 08:22:22 -0800 Subject: [PATCH 1/2] govc: add '-I' flag to ls and find commands Signed-off-by: Doug MacEachern --- govc/USAGE.md | 3 +++ govc/ls/command.go | 15 +++++++++++---- govc/object/find.go | 18 ++++++++++++++++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/govc/USAGE.md b/govc/USAGE.md index ab83f3a62..eb2f514ec 100644 --- a/govc/USAGE.md +++ b/govc/USAGE.md @@ -2600,6 +2600,7 @@ The '-type' flag value can be a managed entity type or one of the following alia Examples: govc find govc find -l / # include object type in output + govc find -l -I / # include MOID in output govc find /dc1 -type c govc find vm -name my-vm-* govc find . -type n @@ -2611,6 +2612,7 @@ Examples: govc find . -type h -hardware.cpuInfo.numCpuCores 16 Options: + -I=false Print the managed object ID -i=false Print the managed object reference -l=false Long listing format -maxdepth=-1 Max depth @@ -4388,6 +4390,7 @@ Examples: govc ls -t Datastore host/ClusterA/* | grep -v local | xargs -n1 basename | sort | uniq Options: + -I=false Print the managed object ID -L=false Follow managed object references -i=false Print the managed object reference -l=false Long listing format diff --git a/govc/ls/command.go b/govc/ls/command.go index 6be82b2f4..d5555c7d1 100644 --- a/govc/ls/command.go +++ b/govc/ls/command.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved. +Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -36,6 +36,7 @@ type ls struct { Long bool Type string ToRef bool + ToID bool DeRef bool } @@ -49,6 +50,7 @@ func (cmd *ls) Register(ctx context.Context, f *flag.FlagSet) { f.BoolVar(&cmd.Long, "l", false, "Long listing format") f.BoolVar(&cmd.ToRef, "i", false, "Print the managed object reference") + f.BoolVar(&cmd.ToID, "I", false, "Print the managed object ID") f.BoolVar(&cmd.DeRef, "L", false, "Follow managed object references") f.StringVar(&cmd.Type, "t", "", "Object type") } @@ -145,8 +147,13 @@ func (l listResult) Write(w io.Writer) error { var err error for _, e := range l.Elements { - if l.ToRef { - fmt.Fprint(w, e.Object.Reference().String()) + if l.ToRef || l.ToID { + ref := e.Object.Reference() + id := ref.String() + if l.ToID { + id = ref.Value + } + fmt.Fprint(w, id) if l.Long { fmt.Fprintf(w, " %s", e.Path) } diff --git a/govc/object/find.go b/govc/object/find.go index e19abed96..94ba1fab5 100644 --- a/govc/object/find.go +++ b/govc/object/find.go @@ -41,6 +41,7 @@ type find struct { *flags.DatacenterFlag ref bool + id bool long bool parent bool kind kinds @@ -128,6 +129,7 @@ func (cmd *find) Register(ctx context.Context, f *flag.FlagSet) { f.StringVar(&cmd.name, "name", "*", "Resource name") f.IntVar(&cmd.maxdepth, "maxdepth", -1, "Max depth") f.BoolVar(&cmd.ref, "i", false, "Print the managed object reference") + f.BoolVar(&cmd.id, "I", false, "Print the managed object ID") f.BoolVar(&cmd.long, "l", false, "Long listing format") f.BoolVar(&cmd.parent, "p", false, "Find parent objects") } @@ -153,6 +155,7 @@ The '-type' flag value can be a managed entity type or one of the following alia Examples: govc find govc find -l / # include object type in output + govc find -l -I / # include MOID in output govc find /dc1 -type c govc find vm -name my-vm-* govc find . -type n @@ -214,6 +217,13 @@ func (cmd *find) writeResult(paths []string) error { return cmd.WriteResult(findResult(paths)) } +func (cmd *find) mo(o types.ManagedObjectReference) string { + if cmd.id { + return o.Value + } + return o.String() +} + func (cmd *find) Run(ctx context.Context, f *flag.FlagSet) error { client, err := cmd.Client() if err != nil { @@ -225,6 +235,10 @@ func (cmd *find) Run(ctx context.Context, f *flag.FlagSet) error { return err } + if cmd.id { + cmd.ref = true + } + root := client.ServiceContent.RootFolder rootPath := "/" @@ -316,7 +330,7 @@ func (cmd *find) Run(ctx context.Context, f *flag.FlagSet) error { printPath := func(o types.ManagedObjectReference, p string) { if cmd.ref && !cmd.long { - paths = append(paths, o.String()) + paths = append(paths, cmd.mo(o)) return } @@ -324,7 +338,7 @@ func (cmd *find) Run(ctx context.Context, f *flag.FlagSet) error { if cmd.long { id := strings.TrimPrefix(o.Type, "Vmware") if cmd.ref { - id = o.String() + id = cmd.mo(o) } path = id + "\t" + path From a9d5985ffaceba5fefd4edc8cb755f9cf91f6c9a Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Sun, 3 Nov 2024 08:22:30 -0800 Subject: [PATCH 2/2] api: support MOID conversion in Finder methods Signed-off-by: Doug MacEachern --- find/doc.go | 7 ++++-- find/finder.go | 23 ++++++++++++------ find/finder_test.go | 47 +++++++++++++++++++++++++++++++++++- govc/flags/datacenter.go | 18 +++++--------- govc/test/object.bats | 36 ++++++++++++++++++++++++++++ object/common.go | 52 +++++++++++++++++++++++++++++++++++++--- object/common_test.go | 28 ++++++++++++++++++++++ simulator/registry.go | 2 ++ vim25/mo/type_info.go | 10 ++++++++ 9 files changed, 198 insertions(+), 25 deletions(-) diff --git a/find/doc.go b/find/doc.go index d22e88353..a42a910a3 100644 --- a/find/doc.go +++ b/find/doc.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2014-2017 VMware, Inc. All Rights Reserved. +Copyright (c) 2017-2022 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -32,6 +32,9 @@ otherwise "find" mode is used. The exception is to use a "..." wildcard with a path to find all objects recursively underneath any root object. For example: VirtualMachineList("/DC1/...") +Finder methods can also convert a managed object reference (aka MOID) to an object instance. +For example: VirtualMachine("VirtualMachine:vm-123") or VirtualMachine("vm-123") + See also: https://github.com/vmware/govmomi/blob/main/govc/README.md#usage */ package find diff --git a/find/finder.go b/find/finder.go index 887ddd5c7..b49495974 100644 --- a/find/finder.go +++ b/find/finder.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2014-2023 VMware, Inc. All Rights Reserved. +Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -22,6 +22,7 @@ import ( "path" "strings" + "github.com/vmware/govmomi/fault" "github.com/vmware/govmomi/internal" "github.com/vmware/govmomi/list" "github.com/vmware/govmomi/object" @@ -116,6 +117,19 @@ func (f *Finder) findRoot(ctx context.Context, root *list.Element, parts []strin func (f *Finder) find(ctx context.Context, arg string, s *spec) ([]list.Element, error) { isPath := strings.Contains(arg, "/") + if !isPath { + if ref := object.ReferenceFromString(arg); ref != nil { + p, err := InventoryPath(ctx, f.client, *ref) + if err == nil { + if t, ok := mo.Value(*ref); ok { + return []list.Element{{Object: t, Path: p}}, nil + } + } else if !fault.Is(err, &types.ManagedObjectNotFound{}) { + return nil, err + } // else fall through to name based lookup + } + } + root := list.Element{ Object: object.NewRootFolder(f.client), Path: "/", @@ -821,11 +835,6 @@ func (f *Finder) Network(ctx context.Context, path string) (object.NetworkRefere } func (f *Finder) networkByID(ctx context.Context, path string) (object.NetworkReference, error) { - if ref := object.ReferenceFromString(path); ref != nil { - // This is a MOID - return object.NewReference(f.client, *ref).(object.NetworkReference), nil - } - kind := []string{"DistributedVirtualPortgroup"} m := view.NewManager(f.client) diff --git a/find/finder_test.go b/find/finder_test.go index 050ca6bd1..7d3b5476e 100644 --- a/find/finder_test.go +++ b/find/finder_test.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -105,3 +105,48 @@ func TestFindNetwork(t *testing.T) { } }, model) } + +func TestFindByID(t *testing.T) { + simulator.Test(func(ctx context.Context, c *vim25.Client) { + find := find.NewFinder(c) + + vms, err := find.VirtualMachineList(ctx, "*") + if err != nil { + t.Fatal(err) + } + + for _, vm := range vms { + ref := vm.Reference() + byRef, err := find.VirtualMachine(ctx, ref.String()) + if err != nil { + t.Fatal(err) + } + if byRef.InventoryPath != vm.InventoryPath { + t.Errorf("InventoryPath=%q", byRef.InventoryPath) + } + if byRef.Reference() != ref { + t.Error(byRef.Reference()) + } + _, err = find.VirtualMachine(ctx, ref.String()+"invalid") + if err == nil { + t.Error("expected error") + } + + byID, err := find.VirtualMachine(ctx, ref.Value) + if err != nil { + t.Error(err) + } + if byID.InventoryPath != vm.InventoryPath { + t.Errorf("InventoryPath=%q", byID.InventoryPath) + } + if byID.Reference() != ref { + t.Error(byID.Reference()) + } + _, err = find.VirtualMachine(ctx, ref.Value+"invalid") + if err == nil { + t.Error("expected error") + } + + } + }) +} diff --git a/govc/flags/datacenter.go b/govc/flags/datacenter.go index 7cfcef3bd..9d5e03ac0 100644 --- a/govc/flags/datacenter.go +++ b/govc/flags/datacenter.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved. +Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -197,16 +197,6 @@ func (flag *DatacenterFlag) ManagedObjects(ctx context.Context, args []string) ( } for _, arg := range args { - if ref := object.ReferenceFromString(arg); ref != nil { - // e.g. output from object.collect - refs = append(refs, *ref) - continue - } - - if !strings.Contains(arg, "/") { - return nil, fmt.Errorf("%q must be qualified with a path", arg) - } - elements, err := finder.ManagedObjectList(ctx, arg) if err != nil { return nil, err @@ -216,6 +206,10 @@ func (flag *DatacenterFlag) ManagedObjects(ctx context.Context, args []string) ( return nil, fmt.Errorf("object '%s' not found", arg) } + if len(elements) > 1 && !strings.Contains(arg, "/") { + return nil, fmt.Errorf("%q must be qualified with a path", arg) + } + for _, e := range elements { refs = append(refs, e.Object.Reference()) } diff --git a/govc/test/object.bats b/govc/test/object.bats index 0fa3928d3..1b6ea7fd3 100755 --- a/govc/test/object.bats +++ b/govc/test/object.bats @@ -493,6 +493,42 @@ EOF assert_success 2 } +@test "collect by id" { + vcsim_env -standalone-host 0 -pod 1 -app 1 + + run govc find -i / + assert_success + + for ref in "${lines[@]}" ; do + run govc collect "$ref" name + assert_success + done + + run govc find -I / + assert_success + + for id in "${lines[@]}" ; do + run govc collect "$id" name + assert_success + done + + run govc find -I -type m / + assert_success + + for id in "${lines[@]}" ; do + run govc vm.info "$id" + assert_success + done + + run govc find -I -type h / + assert_success + + for id in "${lines[@]}" ; do + run govc host.info "$id" + assert_success + done +} + @test "object.find" { vcsim_env -ds 2 diff --git a/object/common.go b/object/common.go index 88ce78fce..11064ac0e 100644 --- a/object/common.go +++ b/object/common.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "path" + "strings" "github.com/vmware/govmomi/property" "github.com/vmware/govmomi/vim25" @@ -136,13 +137,58 @@ func (c Common) SetCustomValue(ctx context.Context, key string, value string) er return err } +var refTypeMap = map[string]string{ + "datacenter": "Datacenter", + "datastore": "Datastore", + "domain": "ComputeResource", + "dvportgroup": "DistributedVirtualPortgroup", + "dvs": "DistributedVirtualSwitch", + "group": "Folder", + "host": "HostSystem", + "network": "Network", + "resgroup": "ResourcePool", + "vm": "VirtualMachine", +} + +// sub types +var prefixTypeMap = map[string]struct{ prefix, kind string }{ + "domain": {"c", "ClusterComputeResource"}, // extends ComputeResource + "group": {"p", "StoragePod"}, // extends Folder + "resgroup": {"v", "VirtualApp"}, // extends ResourcePool +} + +// ReferenceFromString converts a string to ManagedObjectReference. +// First checks for ManagedObjectReference (MOR), in the format of: +// "$Type:$ID", e.g. "Datacenter:datacenter-3" +// Next checks for Managed Object ID (MOID), where type is derived from the ID. +// For example, "datacenter-3" is converted to a MOR "Datacenter:datacenter-3" +// Returns nil if string is not in either format. func ReferenceFromString(s string) *types.ManagedObjectReference { var ref types.ManagedObjectReference - if !ref.FromString(s) { + if ref.FromString(s) && mo.IsManagedObjectType(ref.Type) { + return &ref + } + + id := strings.SplitN(s, "-", 2) + if len(id) != 2 { return nil } - if mo.IsManagedObjectType(ref.Type) { - return &ref + + if kind, ok := refTypeMap[id[0]]; ok { + if p, ok := prefixTypeMap[id[0]]; ok { + if strings.HasPrefix(id[1], p.prefix) { + return &types.ManagedObjectReference{ + Type: p.kind, + Value: s, + } + } + } + + return &types.ManagedObjectReference{ + Type: kind, + Value: s, + } } + return nil } diff --git a/object/common_test.go b/object/common_test.go index fe4239fa9..eba9a16d0 100644 --- a/object/common_test.go +++ b/object/common_test.go @@ -18,11 +18,13 @@ package object_test import ( "context" + "reflect" "testing" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/simulator" "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/types" ) func TestCommonName(t *testing.T) { @@ -63,3 +65,29 @@ func TestObjectName(t *testing.T) { } }) } + +func TestReferenceFromString(t *testing.T) { + tests := []struct { + in string + out *types.ManagedObjectReference + }{ + {"no:no", nil}, + {"Datacenter:yes", &types.ManagedObjectReference{Type: "Datacenter", Value: "yes"}}, + {"datacenter-yes", &types.ManagedObjectReference{Type: "Datacenter", Value: "datacenter-yes"}}, + {"VirtualMachine:vm-2", &types.ManagedObjectReference{Type: "VirtualMachine", Value: "vm-2"}}, + {"vm-2", &types.ManagedObjectReference{Type: "VirtualMachine", Value: "vm-2"}}, + {"domain-s2", &types.ManagedObjectReference{Type: "ComputeResource", Value: "domain-s2"}}, + {"domain-c2", &types.ManagedObjectReference{Type: "ClusterComputeResource", Value: "domain-c2"}}, + {"group-d1", &types.ManagedObjectReference{Type: "Folder", Value: "group-d1"}}, + {"group-p2", &types.ManagedObjectReference{Type: "StoragePod", Value: "group-p2"}}, + {"resgroup-42", &types.ManagedObjectReference{Type: "ResourcePool", Value: "resgroup-42"}}, + {"resgroup-v32", &types.ManagedObjectReference{Type: "VirtualApp", Value: "resgroup-v32"}}, + } + + for _, test := range tests { + ref := object.ReferenceFromString(test.in) + if !reflect.DeepEqual(test.out, ref) { + t.Errorf("%s: expected %v, got %v", test.in, test.out, ref) + } + } +} diff --git a/simulator/registry.go b/simulator/registry.go index 8fbfea183..b40b755c3 100644 --- a/simulator/registry.go +++ b/simulator/registry.go @@ -41,11 +41,13 @@ var refValueMap = map[string]string{ "EnvironmentBrowser": "envbrowser", "HostSystem": "host", "ResourcePool": "resgroup", + "VirtualApp": "resgroup-v", "VirtualMachine": "vm", "VirtualMachineSnapshot": "snapshot", "VmwareDistributedVirtualSwitch": "dvs", "DistributedVirtualSwitch": "dvs", "ClusterComputeResource": "domain-c", + "ComputeResource": "domain-s", "Folder": "group", "StoragePod": "group-p", } diff --git a/vim25/mo/type_info.go b/vim25/mo/type_info.go index 003b56c29..bb2d2e4a5 100644 --- a/vim25/mo/type_info.go +++ b/vim25/mo/type_info.go @@ -337,6 +337,16 @@ func IsManagedObjectType(kind string) bool { return ok } +// Value returns a new mo instance of the given ref Type. +func Value(ref types.ManagedObjectReference) (Reference, bool) { + if rt, ok := t[ref.Type]; ok { + val := reflect.New(rt) + val.Interface().(Entity).Entity().Self = ref + return val.Elem().Interface().(Reference), true + } + return nil, false +} + // Field of a ManagedObject in string form. type Field struct { Path string