Repository: vmware/govmomi (go)

Issue:
[BUG] DatacentersFolders.VmFolder is not a complete path when datacenter is in a datacenter folder.
**Describe the bug**

DatacentersFolders.VmFolder is not a complete path when datacenter is in a datacenter folder.

using this snipit and output as the example

```
	dc, err := finder.Datacenter(ctx, failureDomain.Topology.Datacenter)
        if err != nil {
                return err
        }

        dcFolders, err := dc.Folders(ctx)
        if err != nil {
                return fmt.Errorf("unable to get datacenter folder: %w", err)
        }


        logrus.Debugf("datacenter inventory path: %s", dc.InventoryPath)

        logrus.Debugf("datacenter vm folder inventory path: %s\n", folderPath)

```
output
```
DEBUG datacenter inventory path: /dcfolder/dcfolder1/dcfolder2/nested8-datacenter 
DEBUG datacenter vm folder inventory path: /nested8-datacenter/vm/jcallen3-rlr9v 
```


**To Reproduce**
Steps to reproduce the behavior:

1. always, see above

**Expected behavior**

The DatacenterFolders.VmFolder is the full complete path to `/vm`
(for our example that is `/dcfolder/dcfolder1/dcfolder2/nested8-datacenter/vm`)

**Affected version**
github.com/vmware/govmomi v0.37.2 





Pull request description:
fix: use complete InventoryPath in Datacenter.Folders
The {vm,host,datastore,network} folders are always relative to a Datacenter InventoryPath. However, the Datacenter.Folders method was using the Datacenter name, rather than InventoryPath.

Fixes #3523


Patch:
diff --git a/object/datacenter.go b/object/datacenter.go
index 41fa3526..8caea3eb 100644
--- a/object/datacenter.go
+++ b/object/datacenter.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2015 VMware, Inc. All Rights Reserved.
+Copyright (c) 2015-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,
@@ -18,7 +18,7 @@ package object
 
 import (
 	"context"
-	"fmt"
+	"path"
 
 	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/methods"
@@ -69,8 +69,12 @@ func (d *Datacenter) Folders(ctx context.Context) (*DatacenterFolders, error) {
 		{"network", &df.NetworkFolder.InventoryPath},
 	}
 
+	dcPath := d.InventoryPath
+	if dcPath == "" {
+		dcPath = "/" + md.Name
+	}
 	for _, p := range paths {
-		*p.path = fmt.Sprintf("/%s/%s", md.Name, p.name)
+		*p.path = path.Join(dcPath, p.name)
 	}
 
 	return df, nil


Test patch:
diff --git a/object/datacenter_test.go b/object/datacenter_test.go
index 1eb41057..36033b73 100644
--- a/object/datacenter_test.go
+++ b/object/datacenter_test.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2015 VMware, Inc. All Rights Reserved.
+Copyright (c) 2015-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,
@@ -14,7 +14,60 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package object
+package object_test
+
+import (
+	"context"
+	"testing"
+
+	"github.com/vmware/govmomi/find"
+	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/simulator"
+	"github.com/vmware/govmomi/vim25"
+)
 
 // Datacenter should implement the Reference interface.
-var _ Reference = Datacenter{}
+var _ object.Reference = object.Datacenter{}
+
+func TestDatacenterFolders(t *testing.T) {
+	model := simulator.VPX()
+	model.Datacenter = 2
+	model.Folder = 1
+
+	simulator.Test(func(ctx context.Context, c *vim25.Client) {
+		search := object.NewSearchIndex(c)
+		finder := find.NewFinder(c)
+
+		dc, err := finder.Datacenter(ctx, "DC1")
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		f, err := dc.Folders(ctx)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		folders := []*object.Folder{
+			f.DatastoreFolder,
+			f.HostFolder,
+			f.NetworkFolder,
+			f.VmFolder,
+		}
+
+		for _, folder := range folders {
+			p := "/F0/DC1/" + folder.Name()
+			if p != folder.InventoryPath {
+				t.Errorf("InventoryPath=%s", folder.InventoryPath)
+			}
+
+			ref, err := search.FindByInventoryPath(ctx, folder.InventoryPath)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if ref == nil {
+				t.Errorf("invalid InventoryPath: %s", folder.InventoryPath)
+			}
+		}
+	}, model)
+}