diff --git a/CHANGELOG.md b/CHANGELOG.md index 931b605d7..c138ba5de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # changelog +### unreleased + +* Add DatastorePath helper + ### 0.12.0 (2016-12-01) * Disable use of service ticket for datastore HTTP access by default diff --git a/object/datastore.go b/object/datastore.go index 4daa6ca7b..fc696cdf2 100644 --- a/object/datastore.go +++ b/object/datastore.go @@ -69,12 +69,10 @@ func NewDatastore(c *vim25.Client, ref types.ManagedObjectReference) *Datastore } func (d Datastore) Path(path string) string { - name := d.Name() - if name == "" { - panic("expected non-empty name") - } - - return fmt.Sprintf("[%s] %s", name, path) + return (&DatastorePath{ + Datastore: d.Name(), + Path: path, + }).String() } // NewURL constructs a url.URL with the given file path for datastore access over HTTP. diff --git a/object/datastore_path.go b/object/datastore_path.go new file mode 100644 index 000000000..ea152103d --- /dev/null +++ b/object/datastore_path.go @@ -0,0 +1,65 @@ +/* +Copyright (c) 2016 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 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package object + +import ( + "fmt" + "strings" +) + +// DatastorePath contains the components of a datastore path. +type DatastorePath struct { + Datastore string + Path string +} + +// FromString parses a datastore path. +// Returns true if the path could be parsed, false otherwise. +func (p *DatastorePath) FromString(s string) bool { + if len(s) == 0 { + return false + } + + s = strings.TrimSpace(s) + + if !strings.HasPrefix(s, "[") { + return false + } + + s = s[1:] + + ix := strings.Index(s, "]") + if ix < 0 { + return false + } + + p.Datastore = s[:ix] + p.Path = strings.TrimSpace(s[ix+1:]) + + return true +} + +// String formats a datastore path. +func (p *DatastorePath) String() string { + s := fmt.Sprintf("[%s]", p.Datastore) + + if p.Path == "" { + return s + } + + return strings.Join([]string{s, p.Path}, " ") +} diff --git a/object/datastore_path_test.go b/object/datastore_path_test.go new file mode 100644 index 000000000..ba85f74b2 --- /dev/null +++ b/object/datastore_path_test.go @@ -0,0 +1,76 @@ +/* +Copyright (c) 2016 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 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package object + +import "testing" + +func TestParseDatastorePath(t *testing.T) { + tests := []struct { + dsPath string + dsFile string + fail bool + }{ + {"", "", true}, + {"x", "", true}, + {"[", "", true}, + {"[nope", "", true}, + {"[te st]", "", false}, + {"[te st] foo", "foo", false}, + {"[te st] foo/foo.vmx", "foo/foo.vmx", false}, + {"[te st]foo bar/foo bar.vmx", "foo bar/foo bar.vmx", false}, + {" [te st] bar/bar.vmx ", "bar/bar.vmx", false}, + } + + for _, test := range tests { + p := new(DatastorePath) + ok := p.FromString(test.dsPath) + + if test.fail { + if ok { + t.Errorf("expected error for: %s", test.dsPath) + } + } else { + if !ok { + t.Errorf("failed to parse: %q", test.dsPath) + } else { + if test.dsFile != p.Path { + t.Errorf("dsFile=%s", p.Path) + } + if p.Datastore != "te st" { + t.Errorf("ds=%s", p.Datastore) + } + } + } + } + + s := "[datastore1] foo/bar.vmdk" + p := new(DatastorePath) + ok := p.FromString(s) + if !ok { + t.Fatal(s) + } + + if p.String() != s { + t.Fatal(p.String()) + } + + p.Path = "" + + if p.String() != "[datastore1]" { + t.Fatal(p.String()) + } +} diff --git a/object/virtual_device_list.go b/object/virtual_device_list.go index e81ca4e67..24821aa6b 100644 --- a/object/virtual_device_list.go +++ b/object/virtual_device_list.go @@ -460,12 +460,14 @@ func (l VirtualDeviceList) CreateDisk(c types.BaseVirtualController, ds types.Ma func (l VirtualDeviceList) ChildDisk(parent *types.VirtualDisk) *types.VirtualDisk { disk := *parent backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo) - ds := strings.SplitN(backing.FileName[1:], "]", 2) + p := new(DatastorePath) + p.FromString(backing.FileName) + p.Path = "" // Use specified disk as parent backing to a new disk. disk.Backing = &types.VirtualDiskFlatVer2BackingInfo{ VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ - FileName: fmt.Sprintf("[%s]", ds[0]), + FileName: p.String(), Datastore: backing.Datastore, }, Parent: backing, diff --git a/object/virtual_device_list_test.go b/object/virtual_device_list_test.go index 4d893601b..23bc7da6b 100644 --- a/object/virtual_device_list_test.go +++ b/object/virtual_device_list_test.go @@ -851,3 +851,23 @@ func TestName(t *testing.T) { } } } + +func TestChildDisk(t *testing.T) { + disks := devices.SelectByType((*types.VirtualDisk)(nil)) + + for _, disk := range disks { + child := disks.ChildDisk(disk.(*types.VirtualDisk)) + name := child.Backing.(*types.VirtualDiskFlatVer2BackingInfo).VirtualDeviceFileBackingInfo.FileName + + p := new(DatastorePath) + p.FromString(name) + + if p.Datastore != "datastore1" { + t.Fatal(p.Datastore) + } + + if p.Path != "" { + t.Fatal(p.Path) + } + } +}