Skip to content

Commit a9d5985

Browse files
committed
api: support MOID conversion in Finder methods
Signed-off-by: Doug MacEachern <dougm@broadcom.com>
1 parent 8f3b0a3 commit a9d5985

9 files changed

Lines changed: 198 additions & 25 deletions

File tree

find/doc.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
Copyright (c) 2014-2017 VMware, Inc. All Rights Reserved.
2+
Copyright (c) 2017-2022 VMware, Inc. All Rights Reserved.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
66
You may obtain a copy of the License at
77
8-
http://www.apache.org/licenses/LICENSE-2.0
8+
http://www.apache.org/licenses/LICENSE-2.0
99
1010
Unless required by applicable law or agreed to in writing, software
1111
distributed under the License is distributed on an "AS IS" BASIS,
@@ -32,6 +32,9 @@ otherwise "find" mode is used.
3232
The exception is to use a "..." wildcard with a path to find all objects recursively underneath any root object.
3333
For example: VirtualMachineList("/DC1/...")
3434
35+
Finder methods can also convert a managed object reference (aka MOID) to an object instance.
36+
For example: VirtualMachine("VirtualMachine:vm-123") or VirtualMachine("vm-123")
37+
3538
See also: https://github.com/vmware/govmomi/blob/main/govc/README.md#usage
3639
*/
3740
package find

find/finder.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
Copyright (c) 2014-2023 VMware, Inc. All Rights Reserved.
2+
Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
66
You may obtain a copy of the License at
77
8-
http://www.apache.org/licenses/LICENSE-2.0
8+
http://www.apache.org/licenses/LICENSE-2.0
99
1010
Unless required by applicable law or agreed to in writing, software
1111
distributed under the License is distributed on an "AS IS" BASIS,
@@ -22,6 +22,7 @@ import (
2222
"path"
2323
"strings"
2424

25+
"github.com/vmware/govmomi/fault"
2526
"github.com/vmware/govmomi/internal"
2627
"github.com/vmware/govmomi/list"
2728
"github.com/vmware/govmomi/object"
@@ -116,6 +117,19 @@ func (f *Finder) findRoot(ctx context.Context, root *list.Element, parts []strin
116117
func (f *Finder) find(ctx context.Context, arg string, s *spec) ([]list.Element, error) {
117118
isPath := strings.Contains(arg, "/")
118119

120+
if !isPath {
121+
if ref := object.ReferenceFromString(arg); ref != nil {
122+
p, err := InventoryPath(ctx, f.client, *ref)
123+
if err == nil {
124+
if t, ok := mo.Value(*ref); ok {
125+
return []list.Element{{Object: t, Path: p}}, nil
126+
}
127+
} else if !fault.Is(err, &types.ManagedObjectNotFound{}) {
128+
return nil, err
129+
} // else fall through to name based lookup
130+
}
131+
}
132+
119133
root := list.Element{
120134
Object: object.NewRootFolder(f.client),
121135
Path: "/",
@@ -821,11 +835,6 @@ func (f *Finder) Network(ctx context.Context, path string) (object.NetworkRefere
821835
}
822836

823837
func (f *Finder) networkByID(ctx context.Context, path string) (object.NetworkReference, error) {
824-
if ref := object.ReferenceFromString(path); ref != nil {
825-
// This is a MOID
826-
return object.NewReference(f.client, *ref).(object.NetworkReference), nil
827-
}
828-
829838
kind := []string{"DistributedVirtualPortgroup"}
830839

831840
m := view.NewManager(f.client)

find/finder_test.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
66
You may obtain a copy of the License at
77
8-
http://www.apache.org/licenses/LICENSE-2.0
8+
http://www.apache.org/licenses/LICENSE-2.0
99
1010
Unless required by applicable law or agreed to in writing, software
1111
distributed under the License is distributed on an "AS IS" BASIS,
@@ -105,3 +105,48 @@ func TestFindNetwork(t *testing.T) {
105105
}
106106
}, model)
107107
}
108+
109+
func TestFindByID(t *testing.T) {
110+
simulator.Test(func(ctx context.Context, c *vim25.Client) {
111+
find := find.NewFinder(c)
112+
113+
vms, err := find.VirtualMachineList(ctx, "*")
114+
if err != nil {
115+
t.Fatal(err)
116+
}
117+
118+
for _, vm := range vms {
119+
ref := vm.Reference()
120+
byRef, err := find.VirtualMachine(ctx, ref.String())
121+
if err != nil {
122+
t.Fatal(err)
123+
}
124+
if byRef.InventoryPath != vm.InventoryPath {
125+
t.Errorf("InventoryPath=%q", byRef.InventoryPath)
126+
}
127+
if byRef.Reference() != ref {
128+
t.Error(byRef.Reference())
129+
}
130+
_, err = find.VirtualMachine(ctx, ref.String()+"invalid")
131+
if err == nil {
132+
t.Error("expected error")
133+
}
134+
135+
byID, err := find.VirtualMachine(ctx, ref.Value)
136+
if err != nil {
137+
t.Error(err)
138+
}
139+
if byID.InventoryPath != vm.InventoryPath {
140+
t.Errorf("InventoryPath=%q", byID.InventoryPath)
141+
}
142+
if byID.Reference() != ref {
143+
t.Error(byID.Reference())
144+
}
145+
_, err = find.VirtualMachine(ctx, ref.Value+"invalid")
146+
if err == nil {
147+
t.Error("expected error")
148+
}
149+
150+
}
151+
})
152+
}

govc/flags/datacenter.go

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved.
2+
Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
66
You may obtain a copy of the License at
77
8-
http://www.apache.org/licenses/LICENSE-2.0
8+
http://www.apache.org/licenses/LICENSE-2.0
99
1010
Unless required by applicable law or agreed to in writing, software
1111
distributed under the License is distributed on an "AS IS" BASIS,
@@ -197,16 +197,6 @@ func (flag *DatacenterFlag) ManagedObjects(ctx context.Context, args []string) (
197197
}
198198

199199
for _, arg := range args {
200-
if ref := object.ReferenceFromString(arg); ref != nil {
201-
// e.g. output from object.collect
202-
refs = append(refs, *ref)
203-
continue
204-
}
205-
206-
if !strings.Contains(arg, "/") {
207-
return nil, fmt.Errorf("%q must be qualified with a path", arg)
208-
}
209-
210200
elements, err := finder.ManagedObjectList(ctx, arg)
211201
if err != nil {
212202
return nil, err
@@ -216,6 +206,10 @@ func (flag *DatacenterFlag) ManagedObjects(ctx context.Context, args []string) (
216206
return nil, fmt.Errorf("object '%s' not found", arg)
217207
}
218208

209+
if len(elements) > 1 && !strings.Contains(arg, "/") {
210+
return nil, fmt.Errorf("%q must be qualified with a path", arg)
211+
}
212+
219213
for _, e := range elements {
220214
refs = append(refs, e.Object.Reference())
221215
}

govc/test/object.bats

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,42 @@ EOF
493493
assert_success 2
494494
}
495495

496+
@test "collect by id" {
497+
vcsim_env -standalone-host 0 -pod 1 -app 1
498+
499+
run govc find -i /
500+
assert_success
501+
502+
for ref in "${lines[@]}" ; do
503+
run govc collect "$ref" name
504+
assert_success
505+
done
506+
507+
run govc find -I /
508+
assert_success
509+
510+
for id in "${lines[@]}" ; do
511+
run govc collect "$id" name
512+
assert_success
513+
done
514+
515+
run govc find -I -type m /
516+
assert_success
517+
518+
for id in "${lines[@]}" ; do
519+
run govc vm.info "$id"
520+
assert_success
521+
done
522+
523+
run govc find -I -type h /
524+
assert_success
525+
526+
for id in "${lines[@]}" ; do
527+
run govc host.info "$id"
528+
assert_success
529+
done
530+
}
531+
496532
@test "object.find" {
497533
vcsim_env -ds 2
498534

object/common.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"errors"
2222
"fmt"
2323
"path"
24+
"strings"
2425

2526
"github.com/vmware/govmomi/property"
2627
"github.com/vmware/govmomi/vim25"
@@ -136,13 +137,58 @@ func (c Common) SetCustomValue(ctx context.Context, key string, value string) er
136137
return err
137138
}
138139

140+
var refTypeMap = map[string]string{
141+
"datacenter": "Datacenter",
142+
"datastore": "Datastore",
143+
"domain": "ComputeResource",
144+
"dvportgroup": "DistributedVirtualPortgroup",
145+
"dvs": "DistributedVirtualSwitch",
146+
"group": "Folder",
147+
"host": "HostSystem",
148+
"network": "Network",
149+
"resgroup": "ResourcePool",
150+
"vm": "VirtualMachine",
151+
}
152+
153+
// sub types
154+
var prefixTypeMap = map[string]struct{ prefix, kind string }{
155+
"domain": {"c", "ClusterComputeResource"}, // extends ComputeResource
156+
"group": {"p", "StoragePod"}, // extends Folder
157+
"resgroup": {"v", "VirtualApp"}, // extends ResourcePool
158+
}
159+
160+
// ReferenceFromString converts a string to ManagedObjectReference.
161+
// First checks for ManagedObjectReference (MOR), in the format of:
162+
// "$Type:$ID", e.g. "Datacenter:datacenter-3"
163+
// Next checks for Managed Object ID (MOID), where type is derived from the ID.
164+
// For example, "datacenter-3" is converted to a MOR "Datacenter:datacenter-3"
165+
// Returns nil if string is not in either format.
139166
func ReferenceFromString(s string) *types.ManagedObjectReference {
140167
var ref types.ManagedObjectReference
141-
if !ref.FromString(s) {
168+
if ref.FromString(s) && mo.IsManagedObjectType(ref.Type) {
169+
return &ref
170+
}
171+
172+
id := strings.SplitN(s, "-", 2)
173+
if len(id) != 2 {
142174
return nil
143175
}
144-
if mo.IsManagedObjectType(ref.Type) {
145-
return &ref
176+
177+
if kind, ok := refTypeMap[id[0]]; ok {
178+
if p, ok := prefixTypeMap[id[0]]; ok {
179+
if strings.HasPrefix(id[1], p.prefix) {
180+
return &types.ManagedObjectReference{
181+
Type: p.kind,
182+
Value: s,
183+
}
184+
}
185+
}
186+
187+
return &types.ManagedObjectReference{
188+
Type: kind,
189+
Value: s,
190+
}
146191
}
192+
147193
return nil
148194
}

object/common_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ package object_test
1818

1919
import (
2020
"context"
21+
"reflect"
2122
"testing"
2223

2324
"github.com/vmware/govmomi/object"
2425
"github.com/vmware/govmomi/simulator"
2526
"github.com/vmware/govmomi/vim25"
27+
"github.com/vmware/govmomi/vim25/types"
2628
)
2729

2830
func TestCommonName(t *testing.T) {
@@ -63,3 +65,29 @@ func TestObjectName(t *testing.T) {
6365
}
6466
})
6567
}
68+
69+
func TestReferenceFromString(t *testing.T) {
70+
tests := []struct {
71+
in string
72+
out *types.ManagedObjectReference
73+
}{
74+
{"no:no", nil},
75+
{"Datacenter:yes", &types.ManagedObjectReference{Type: "Datacenter", Value: "yes"}},
76+
{"datacenter-yes", &types.ManagedObjectReference{Type: "Datacenter", Value: "datacenter-yes"}},
77+
{"VirtualMachine:vm-2", &types.ManagedObjectReference{Type: "VirtualMachine", Value: "vm-2"}},
78+
{"vm-2", &types.ManagedObjectReference{Type: "VirtualMachine", Value: "vm-2"}},
79+
{"domain-s2", &types.ManagedObjectReference{Type: "ComputeResource", Value: "domain-s2"}},
80+
{"domain-c2", &types.ManagedObjectReference{Type: "ClusterComputeResource", Value: "domain-c2"}},
81+
{"group-d1", &types.ManagedObjectReference{Type: "Folder", Value: "group-d1"}},
82+
{"group-p2", &types.ManagedObjectReference{Type: "StoragePod", Value: "group-p2"}},
83+
{"resgroup-42", &types.ManagedObjectReference{Type: "ResourcePool", Value: "resgroup-42"}},
84+
{"resgroup-v32", &types.ManagedObjectReference{Type: "VirtualApp", Value: "resgroup-v32"}},
85+
}
86+
87+
for _, test := range tests {
88+
ref := object.ReferenceFromString(test.in)
89+
if !reflect.DeepEqual(test.out, ref) {
90+
t.Errorf("%s: expected %v, got %v", test.in, test.out, ref)
91+
}
92+
}
93+
}

simulator/registry.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,13 @@ var refValueMap = map[string]string{
4141
"EnvironmentBrowser": "envbrowser",
4242
"HostSystem": "host",
4343
"ResourcePool": "resgroup",
44+
"VirtualApp": "resgroup-v",
4445
"VirtualMachine": "vm",
4546
"VirtualMachineSnapshot": "snapshot",
4647
"VmwareDistributedVirtualSwitch": "dvs",
4748
"DistributedVirtualSwitch": "dvs",
4849
"ClusterComputeResource": "domain-c",
50+
"ComputeResource": "domain-s",
4951
"Folder": "group",
5052
"StoragePod": "group-p",
5153
}

vim25/mo/type_info.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,16 @@ func IsManagedObjectType(kind string) bool {
337337
return ok
338338
}
339339

340+
// Value returns a new mo instance of the given ref Type.
341+
func Value(ref types.ManagedObjectReference) (Reference, bool) {
342+
if rt, ok := t[ref.Type]; ok {
343+
val := reflect.New(rt)
344+
val.Interface().(Entity).Entity().Self = ref
345+
return val.Elem().Interface().(Reference), true
346+
}
347+
return nil, false
348+
}
349+
340350
// Field of a ManagedObject in string form.
341351
type Field struct {
342352
Path string

0 commit comments

Comments
 (0)