Repository: vmware/govmomi
Pull request #2336 (2021-03-30 03:27:40)

govc: add session.ls -S flag
A principal may not have permission to view all sessions, but it can always view its own.

- Add MissingSet + Faults to -verbose PropertyCollector output


Patch:
diff --git a/go.mod b/go.mod
index f49b3c1f..d1badde5 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,6 @@ replace github.com/davecgh/go-xdr => github.com/rasky/go-xdr v0.0.0-201702171721
 replace github.com/kr/pretty v0.1.0 => github.com/dougm/pretty v0.0.0-20171025230240-2ee9d7453c02
 
 require (
-	github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d
 	github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892
 	github.com/google/uuid v0.0.0-20170306145142-6a5e28554805
 	github.com/kr/pretty v0.1.0
diff --git a/go.sum b/go.sum
index e0a5f5b0..72b4a8ed 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,3 @@
-github.com/a8m/tree v0.0.0-20181222104329-6a0b80129de4 h1:mK1/QgFPU4osbhjJ26B1w738kjQHaGJcon8uCLMS8fk=
-github.com/a8m/tree v0.0.0-20181222104329-6a0b80129de4/go.mod h1:FSdwKX97koS5efgm8WevNf7XS3PqtyFkKDDXrz778cg=
-github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d h1:4E8RufAN3UQ/weB6AnQ4y5miZCO0Yco8ZdGId41WuQs=
-github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d/go.mod h1:FSdwKX97koS5efgm8WevNf7XS3PqtyFkKDDXrz778cg=
 github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892 h1:qg9VbHo1TlL0KDM0vYvBG9EY0X0Yku5WYIPoFWt8f6o=
 github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE=
 github.com/dougm/pretty v0.0.0-20171025230240-2ee9d7453c02 h1:tR3jsKPiO/mb6ntzk/dJlHZtm37CPfVp1C9KIo534+4=
diff --git a/govc/cluster/stretch.go b/govc/cluster/stretch.go
deleted file mode 100644
index 02473d84..00000000
--- a/govc/cluster/stretch.go
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
-Copyright (c) 2021 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 cluster
-
-import (
-	"context"
-	"flag"
-	"fmt"
-	"strings"
-
-	"github.com/vmware/govmomi/govc/cli"
-	"github.com/vmware/govmomi/govc/flags"
-	"github.com/vmware/govmomi/object"
-	vim "github.com/vmware/govmomi/vim25/types"
-	"github.com/vmware/govmomi/vsan"
-	"github.com/vmware/govmomi/vsan/methods"
-	"github.com/vmware/govmomi/vsan/types"
-)
-
-type stretch struct {
-	*flags.DatacenterFlag
-
-	WitnessHost            string
-	FirstFaultDomainHosts  string
-	SecondFaultDomainHosts string
-	FirstFaultDomainName   string
-	SecondFaultDomainName  string
-	PreferredFaultDomain   string
-}
-
-func init() {
-	cli.Register("cluster.stretch", &stretch{})
-}
-
-func (cmd *stretch) Usage() string {
-	return "CLUSTER"
-}
-
-func (cmd *stretch) Description() string {
-	return `Convert a vSAN cluster into a stretched cluster
-
-The vSAN cluster is converted to a stretched cluster with a witness host
-specified by the 'witness' flag.  The datastore hosts are placed into one
-of two fault domains that are specified in each host list. The name of the
-preferred fault domain can be specified by the 'preferred-fault-domain' flag.
-
-Examples:
-  govc cluster.stretch -dc=remote-site-1 -witness=/datacenter/host/192.168.112.2 -first-fault-domain-hosts=192.168.113.121 -second-fault-domain-hosts=192.168.113.45,192.168.113.70 cluster-name
-`
-}
-
-func (cmd *stretch) Register(ctx context.Context, f *flag.FlagSet) {
-	cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx)
-	cmd.DatacenterFlag.Register(ctx, f)
-
-	f.StringVar(&cmd.WitnessHost, "witness", "", "Witness host for the stretched cluster")
-	f.StringVar(&cmd.FirstFaultDomainHosts, "first-fault-domain-hosts", "", "Hosts to place in the first fault domain")
-	f.StringVar(&cmd.SecondFaultDomainHosts, "second-fault-domain-hosts", "", "Hosts to place in the second fault domain")
-	f.StringVar(&cmd.FirstFaultDomainName, "first-fault-domain-name", "Primary", "Name of the first fault domain")
-	f.StringVar(&cmd.SecondFaultDomainName, "second-fault-domain-name", "Secondary", "Name of the second fault domain")
-	f.StringVar(&cmd.PreferredFaultDomain, "preferred-fault-domain", "Primary", "Name of the preferred fault domain")
-}
-
-func (cmd *stretch) Process(ctx context.Context) error {
-	if err := cmd.DatacenterFlag.Process(ctx); err != nil {
-		return err
-	}
-
-	if cmd.WitnessHost == "" ||
-		cmd.FirstFaultDomainHosts == "" ||
-		cmd.FirstFaultDomainName == "" ||
-		cmd.SecondFaultDomainHosts == "" ||
-		cmd.SecondFaultDomainName == "" ||
-		cmd.PreferredFaultDomain == "" {
-		return flag.ErrHelp
-	}
-
-	return nil
-}
-
-func (cmd *stretch) Run(ctx context.Context, f *flag.FlagSet) error {
-	if f.NArg() != 1 {
-		return flag.ErrHelp
-	}
-
-	client, err := cmd.Client()
-	if err != nil {
-		return err
-	}
-
-	vsanClient, err := vsan.NewClient(ctx, client)
-	if err != nil {
-		return err
-	}
-
-	finder, err := cmd.Finder()
-	if err != nil {
-		return err
-	}
-
-	clusterResource, err := finder.ClusterComputeResource(ctx, f.Arg(0))
-	if err != nil {
-		return err
-	}
-
-	witnessHost, err := finder.HostSystem(ctx, cmd.WitnessHost)
-	if err != nil {
-		return err
-	}
-
-	faultDomainConfig, err := cmd.buildFaultDomainConfig(ctx)
-	if err != nil {
-		return err
-	}
-
-	req := types.VSANVcConvertToStretchedCluster{
-		This:              vsan.VsanVcStretchedClusterSystem,
-		Cluster:           clusterResource.Reference(),
-		FaultDomainConfig: *faultDomainConfig,
-		WitnessHost:       witnessHost.Reference(),
-		PreferredFd:       cmd.PreferredFaultDomain,
-		DiskMapping:       nil,
-	}
-
-	res, err := methods.VSANVcConvertToStretchedCluster(ctx, vsanClient, &req)
-	if err != nil {
-		return err
-	}
-
-	logger := cmd.ProgressLogger("stretching cluster... ")
-	defer logger.Wait()
-
-	task := object.NewTask(client, res.Returnval)
-	_, err = task.WaitForResult(ctx, logger)
-	return err
-}
-
-func (cmd *stretch) buildFaultDomainConfig(ctx context.Context) (*types.VimClusterVSANStretchedClusterFaultDomainConfig, error) {
-	var faultDomainConfig types.VimClusterVSANStretchedClusterFaultDomainConfig
-	var err error
-
-	faultDomainConfig.FirstFdName = cmd.FirstFaultDomainName
-	faultDomainConfig.FirstFdHosts, err = cmd.getManagedObjectRefs(cmd.FirstFaultDomainHosts, ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	if len(faultDomainConfig.FirstFdHosts) == 0 {
-		return nil, fmt.Errorf("no hosts for fault domain %q", cmd.FirstFaultDomainName)
-	}
-
-	faultDomainConfig.SecondFdName = cmd.SecondFaultDomainName
-	faultDomainConfig.SecondFdHosts, err = cmd.getManagedObjectRefs(cmd.SecondFaultDomainHosts, ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	if len(faultDomainConfig.SecondFdHosts) == 0 {
-		return nil, fmt.Errorf("no hosts for fault domain %q", cmd.SecondFaultDomainName)
-	}
-
-	return &faultDomainConfig, nil
-}
-
-func (cmd *stretch) getManagedObjectRefs(domainHosts string, ctx context.Context) ([]vim.ManagedObjectReference, error) {
-	finder, err := cmd.Finder()
-	if err != nil {
-		return nil, err
-	}
-
-	var refs []vim.ManagedObjectReference
-	for _, host := range strings.Split(domainHosts, ",") {
-		h, err := finder.HostSystem(ctx, host)
-		if err != nil {
-			return nil, err
-		}
-
-		refs = append(refs, h.Reference())
-	}
-
-	return refs, nil
-}
diff --git a/govc/flags/debug.go b/govc/flags/debug.go
index 7ecd1a97..b02e6a0f 100644
--- a/govc/flags/debug.go
+++ b/govc/flags/debug.go
@@ -336,6 +336,14 @@ func (v *verbose) propertyValue(obj types.ManagedObjectReference, name string, p
 	return fmt.Sprintf("%s\t%s:\t%s", v.mor(obj), name, val)
 }
 
+func (v *verbose) missingSet(o types.ManagedObjectReference, m []types.MissingProperty) []string {
+	var s []string
+	for _, p := range m {
+		s = append(s, fmt.Sprintf("%s\t%s:\t%s", v.mor(o), p.Path, v.prettyPrint(p.Fault.Fault)))
+	}
+	return s
+}
+
 func (v *verbose) updateSet(u *types.UpdateSet) []string {
 	var s []string
 	for _, f := range u.FilterSet {
@@ -343,6 +351,7 @@ func (v *verbose) updateSet(u *types.UpdateSet) []string {
 			for _, c := range o.ChangeSet {
 				s = append(s, v.propertyValue(o.Obj, c.Name, c.Val))
 			}
+			s = append(s, v.missingSet(o.Obj, o.MissingSet)...)
 		}
 	}
 	return s
@@ -354,12 +363,16 @@ func (v *verbose) objectContent(content []types.ObjectContent) []string {
 		for _, p := range o.PropSet {
 			s = append(s, v.propertyValue(o.Obj, p.Name, p.Val))
 		}
+		s = append(s, v.missingSet(o.Obj, o.MissingSet)...)
 	}
 	return s
 }
 
 func (v *verbose) prettyPrint(val interface{}) string {
 	p := pretty.Sprintf("%# v\n", val)
+	if len(p) > 640 {
+		return p // don't bother pruning
+	}
 	var res []string
 	scanner := bufio.NewScanner(strings.NewReader(p))
 	for scanner.Scan() {
@@ -370,6 +383,9 @@ func (v *verbose) prettyPrint(val interface{}) string {
 		if strings.Contains(line, `"",`) {
 			continue // empty string field
 		}
+		if strings.Contains(line, `{},`) {
+			continue // empty embedded struct
+		}
 		if strings.Contains(line, "[context]") {
 			continue // noisy base64 encoded backtrace
 		}
diff --git a/govc/object/tree.go b/govc/object/tree.go
deleted file mode 100644
index ec2652c4..00000000
--- a/govc/object/tree.go
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
-Copyright (c) 2014-2015 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 (
-	"context"
-	"flag"
-	"fmt"
-	"net/url"
-	"os"
-	gopath "path"
-	"time"
-
-	gotree "github.com/a8m/tree"
-
-	"github.com/vmware/govmomi/govc/cli"
-	"github.com/vmware/govmomi/govc/flags"
-	"github.com/vmware/govmomi/view"
-	"github.com/vmware/govmomi/vim25"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-type tree struct {
-	*flags.DatacenterFlag
-
-	long  bool
-	kind  bool
-	color bool
-	level int
-}
-
-func init() {
-	cli.Register("tree", &tree{})
-}
-
-func (cmd *tree) Register(ctx context.Context, f *flag.FlagSet) {
-	cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx)
-	cmd.DatacenterFlag.Register(ctx, f)
-
-	f.BoolVar(&cmd.color, "C", false, "Colorize output")
-	f.BoolVar(&cmd.long, "l", false, "Follow runtime references (e.g. HostSystem VMs)")
-	f.BoolVar(&cmd.kind, "p", false, "Print the object type")
-	f.IntVar(&cmd.level, "L", 0, "Max display depth of the inventory tree")
-}
-
-func (cmd *tree) Description() string {
-	return `List contents of the inventory in a tree-like format.
-
-Examples:
-  govc tree -C /
-  govc tree /datacenter/vm`
-}
-
-func (cmd *tree) Usage() string {
-	return "[PATH]"
-}
-
-func (cmd *tree) Run(ctx context.Context, f *flag.FlagSet) error {
-	c, err := cmd.Client()
-	if err != nil {
-		return err
-	}
-
-	path := f.Arg(0)
-	if path == "" {
-		path = "/"
-	}
-
-	vfs := &virtualFileSystem{
-		ctx:   ctx,
-		cmd:   cmd,
-		c:     c,
-		m:     view.NewManager(c),
-		names: make(map[types.ManagedObjectReference]string),
-		dvs:   make(map[types.ManagedObjectReference][]types.ManagedObjectReference),
-		path:  path,
-	}
-
-	treeOpts := &gotree.Options{
-		Fs:        vfs,
-		OutFile:   cmd.Out,
-		Colorize:  cmd.color,
-		Color:     color,
-		DeepLevel: cmd.level,
-	}
-
-	inf := gotree.New(path)
-	inf.Visit(treeOpts)
-	inf.Print(treeOpts)
-
-	return nil
-}
-
-type virtualFileSystem struct {
-	ctx   context.Context
-	cmd   *tree
-	c     *vim25.Client
-	m     *view.Manager
-	names map[types.ManagedObjectReference]string
-	dvs   map[types.ManagedObjectReference][]types.ManagedObjectReference
-	root  types.ManagedObjectReference
-	path  string
-}
-
-func style(kind string) string {
-	switch kind {
-	case "VirtualMachine":
-		return "1;32"
-	case "HostSystem":
-		return "1;33"
-	case "ResourcePool":
-		return "1;30"
-	case "Network", "OpaqueNetwork", "DistributedVirtualPortgroup":
-		return "1;35"
-	case "Datastore":
-		return "1;36"
-	case "Datacenter":
-		return "1;37"
-	default:
-		return ""
-	}
-}
-
-func color(node *gotree.Node, s string) string {
-	ref := pathReference(node.Path())
-
-	switch ref.Type {
-	case "ResourcePool":
-		return s
-	}
-
-	c := style(ref.Type)
-	if c == "" {
-		return gotree.ANSIColor(node, s)
-	}
-
-	return gotree.ANSIColorFormat(c, s)
-}
-
-func (vfs *virtualFileSystem) Stat(path string) (os.FileInfo, error) {
-	var ref types.ManagedObjectReference
-
-	if len(vfs.names) == 0 {
-		// This is the first Stat() call, where path is the initial user input
-		if path == "/" {
-			ref = vfs.c.ServiceContent.RootFolder
-		} else {
-			var err error
-			ref, err = vfs.cmd.ManagedObject(vfs.ctx, path)
-			if err != nil {
-				return nil, err
-			}
-		}
-		vfs.names[ref] = path
-		vfs.root = ref
-	} else {
-		// The Node.Path in subsequent calls to Stat() will have a MOR base
-		ref = pathReference(path)
-	}
-
-	name := vfs.names[ref]
-
-	var mode os.FileMode
-	switch ref.Type {
-	case "ComputeResource",
-		"ClusterComputeResource",
-		"Datacenter",
-		"Folder",
-		"ResourcePool",
-		"VirtualApp",
-		"StoragePod",
-		"DistributedVirtualSwitch",
-		"VmwareDistributedVirtualSwitch":
-		mode = os.ModeDir
-	case "HostSystem":
-		if vfs.cmd.long {
-			mode = os.ModeDir
-		}
-	}
-
-	if vfs.cmd.kind {
-		name = fmt.Sprintf("[%s] %s", ref.Type, name)
-	}
-
-	return fileInfo{name: name, mode: mode}, nil
-}
-
-// pathReference converts the base of the given Node.Path to a MOR
-func pathReference(s string) types.ManagedObjectReference {
-	var ref types.ManagedObjectReference
-	r, _ := url.PathUnescape(gopath.Base(s))
-	ref.FromString(r)
-	return ref
-}
-
-func (vfs *virtualFileSystem) ReadDir(path string) ([]string, error) {
-	var ref types.ManagedObjectReference
-
-	if path == vfs.path {
-		// This path is the initial user input (e.g. "/" or "/dc1")
-		ref = vfs.root
-	} else {
-		// This path will have had 1 or more MORs appended to it, as returned by this func
-		ref = pathReference(path)
-	}
-
-	var childPaths []string
-
-	switch ref.Type {
-	// In the vCenter inventory switches and portgroups are siblings, hack to display them as parent child in the tree
-	case "DistributedVirtualSwitch", "VmwareDistributedVirtualSwitch":
-		pgs := vfs.dvs[ref]
-		for _, pg := range pgs {
-			childPaths = append(childPaths, url.PathEscape(pg.String()))
-		}
-		return childPaths, nil
-	}
-
-	v, err := vfs.m.CreateContainerView(vfs.ctx, ref, nil, false)
-	if err != nil {
-		return nil, err
-	}
-	defer v.Destroy(vfs.ctx)
-
-	var kind []string
-	if !vfs.cmd.long {
-		switch ref.Type {
-		case "HostSystem":
-			return nil, nil
-		case "ResourcePool", "VirtualApp":
-			kind = []string{"ResourcePool", "VirtualApp"}
-		}
-	}
-
-	var children []types.ObjectContent
-
-	pspec := []types.PropertySpec{
-		{Type: "DistributedVirtualSwitch", PathSet: []string{"portgroup"}},
-		{Type: "VmwareDistributedVirtualSwitch", PathSet: []string{"portgroup"}},
-	}
-
-	err = v.Retrieve(vfs.ctx, kind, []string{"name"}, &children, pspec...)
-	if err != nil {
-		return nil, err
-	}
-
-	for _, content := range children {
-		ref = content.Obj
-		for _, p := range content.PropSet {
-			switch p.Name {
-			case "name":
-				vfs.names[ref] = p.Val.(string)
-			case "portgroup":
-				vfs.dvs[ref] = p.Val.(types.ArrayOfManagedObjectReference).ManagedObjectReference
-			}
-		}
-		if ref.Type == "DistributedVirtualPortgroup" {
-			continue // Returned on ReadDir() of the DVS above
-		}
-		childPaths = append(childPaths, url.PathEscape(ref.String()))
-	}
-
-	return childPaths, nil
-}
-
-type fileInfo struct {
-	name string
-	mode os.FileMode
-}
-
-func (f fileInfo) Name() string {
-	return f.name
-}
-func (f fileInfo) Size() int64 {
-	return 0
-}
-func (f fileInfo) Mode() os.FileMode {
-	return f.mode
-}
-func (f fileInfo) ModTime() time.Time {
-	return time.Now()
-}
-func (f fileInfo) IsDir() bool {
-	return f.mode&os.ModeDir == os.ModeDir
-}
-func (f fileInfo) Sys() interface{} {
-	return nil
-}
diff --git a/govc/session/ls.go b/govc/session/ls.go
index 7346a603..c38ecb11 100644
--- a/govc/session/ls.go
+++ b/govc/session/ls.go
@@ -38,6 +38,7 @@ type ls struct {
 	*flags.OutputFlag
 
 	r bool
+	s bool
 }
 
 func init() {
@@ -51,12 +52,15 @@ func (cmd *ls) Register(ctx context.Context, f *flag.FlagSet) {
 	cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx)
 	cmd.OutputFlag.Register(ctx, f)
 
-	f.BoolVar(&cmd.r, "r", false, "Include current REST session (if any)")
+	f.BoolVar(&cmd.r, "r", false, "List cached REST session (if any)")
+	f.BoolVar(&cmd.s, "S", false, "List current SOAP session")
 }
 
 func (cmd *ls) Description() string {
 	return `List active sessions.
 
+All SOAP sessions are listed by default. The '-S' flag will limit this list to the current session.
+
 Examples:
   govc session.ls
   govc session.ls -json | jq -r .CurrentSession.Key`
@@ -119,10 +123,19 @@ func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error {
 	}
 
 	var m mo.SessionManager
+	var props []string
 	pc := property.DefaultCollector(c)
-	err = pc.RetrieveOne(ctx, *c.ServiceContent.SessionManager, nil, &m)
+	if cmd.s {
+		props = []string{"currentSession"}
+	}
+
+	err = pc.RetrieveOne(ctx, *c.ServiceContent.SessionManager, props, &m)
 	if err != nil {
-		return nil
+		return err
+	}
+
+	if cmd.s {
+		m.SessionList = []types.UserSession{*m.CurrentSession}
 	}
 
 	now, err := methods.GetCurrentTime(ctx, c)
diff --git a/vendor/github.com/a8m/tree/.gitignore b/vendor/github.com/a8m/tree/.gitignore
deleted file mode 100644
index 793bf2eb..00000000
--- a/vendor/github.com/a8m/tree/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-draft
-coverage
diff --git a/vendor/github.com/a8m/tree/.travis.yml b/vendor/github.com/a8m/tree/.travis.yml
deleted file mode 100644
index dbac1c6a..00000000
--- a/vendor/github.com/a8m/tree/.travis.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-arch:
-  - amd64
-  - ppc64le
-
-language: go
-sudo: false
-go:
-  - 1.13.x
-  - 1.14.x
-  - 1.15.x
-  - tip
-matrix:
-  allow_failures:
-  - go: tip
-install:
-  - go get -t -v ./...
-script:
-  - go test -v ./...
-  - ./compileall.sh
diff --git a/vendor/github.com/a8m/tree/LICENSE b/vendor/github.com/a8m/tree/LICENSE
deleted file mode 100644
index fc36360a..00000000
--- a/vendor/github.com/a8m/tree/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2015-2017 Ariel Mashraki
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/vendor/github.com/a8m/tree/README.md b/vendor/github.com/a8m/tree/README.md
deleted file mode 100644
index c79cc8eb..00000000
--- a/vendor/github.com/a8m/tree/README.md
+++ /dev/null
@@ -1,43 +0,0 @@
-tree [![Build status][travis-image]][travis-url] [![License][license-image]][license-url]
----
-> An implementation of the [`tree`](http://mama.indstate.edu/users/ice/tree/) command written in Go, that can be used  programmatically.
-
-<img src="https://raw.githubusercontent.com/a8m/tree/assets/assets/tree.png" height="300" alt="tree command">
-
-#### Installation:
-```sh
-$ go get github.com/a8m/tree/cmd/tree
-```
-
-#### How to use `tree` programmatically ?
-You can take a look on [`cmd/tree`](https://github.com/a8m/tree/blob/master/cmd/tree/tree.go), and [s3tree](http://github.com/a8m/s3tree) or see the example below.
-```go
-import (
-    "github.com/a8m/tree"
-)
-
-func main() {
-    opts := &tree.Options{
-        // Fs, and OutFile are required fields.
-        // fs should implement the tree file-system interface(see: tree.Fs),
-        // and OutFile should be type io.Writer
-        Fs: fs,
-        OutFile: os.Stdout,
-        // ...
-    }
-    inf.New("root-dir")
-    // Visit all nodes recursively
-    inf.Visit(opts)
-    // Print nodes 
-    inf.Print(opts)
-}
-```
-
-### License
-MIT
-
-
-[travis-image]: https://img.shields.io/travis/a8m/tree.svg?style=flat-square
-[travis-url]: https://travis-ci.org/a8m/tree
-[license-image]: http://img.shields.io/npm/l/deep-keys.svg?style=flat-square
-[license-url]: LICENSE
diff --git a/vendor/github.com/a8m/tree/color.go b/vendor/github.com/a8m/tree/color.go
deleted file mode 100644
index 2dffcceb..00000000
--- a/vendor/github.com/a8m/tree/color.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package tree
-
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-	"strings"
-)
-
-const Escape = "\x1b"
-const (
-	Reset int = 0
-	// Not used, remove.
-	Bold  int = 1
-	Black int = iota + 28
-	Red
-	Green
-	Yellow
-	Blue
-	Magenta
-	Cyan
-	White
-)
-
-// ANSIColorFormat
-func ANSIColorFormat(style string, s string) string {
-	return fmt.Sprintf("%s[%sm%s%s[%dm", Escape, style, s, Escape, Reset)
-}
-
-// ANSIColor
-func ANSIColor(node *Node, s string) string {
-	var style string
-	var mode = node.Mode()
-	var ext = filepath.Ext(node.Name())
-	switch {
-	case contains([]string{".bat", ".btm", ".cmd", ".com", ".dll", ".exe"}, ext):
-		style = "1;32"
-	case contains([]string{".arj", ".bz2", ".deb", ".gz", ".lzh", ".rpm",
-		".tar", ".taz", ".tb2", ".tbz2", ".tbz", ".tgz", ".tz", ".tz2", ".z",
-		".zip", ".zoo"}, ext):
-		style = "1;31"
-	case contains([]string{".asf", ".avi", ".bmp", ".flac", ".gif", ".jpg",
-		"jpeg", ".m2a", ".m2v", ".mov", ".mp3", ".mpeg", ".mpg", ".ogg", ".ppm",
-		".rm", ".tga", ".tif", ".wav", ".wmv",
-		".xbm", ".xpm"}, ext):
-		style = "1;35"
-	case node.IsDir() || mode&os.ModeDir != 0:
-		style = "1;34"
-	case mode&os.ModeNamedPipe != 0:
-		style = "40;33"
-	case mode&os.ModeSocket != 0:
-		style = "40;1;35"
-	case mode&os.ModeDevice != 0 || mode&os.ModeCharDevice != 0:
-		style = "40;1;33"
-	case mode&os.ModeSymlink != 0:
-		if _, err := filepath.EvalSymlinks(node.path); err != nil {
-			style = "40;1;31"
-		} else {
-			style = "1;36"
-		}
-	case mode&modeExecute != 0:
-		style = "1;32"
-	default:
-		return s
-	}
-	return ANSIColorFormat(style, s)
-}
-
-// case-insensitive contains helper
-func contains(slice []string, str string) bool {
-	for _, val := range slice {
-		if val == strings.ToLower(str) {
-			return true
-		}
-	}
-	return false
-}
-
-// TODO: HTMLColor
diff --git a/vendor/github.com/a8m/tree/compileall.sh b/vendor/github.com/a8m/tree/compileall.sh
deleted file mode 100644
index 1b4a1011..00000000
--- a/vendor/github.com/a8m/tree/compileall.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-
-go tool dist list >/dev/null || {
-    echo 1>&2 "go tool dist list not supported - can't check compile"
-    exit 0
-}
-
-failures=0
-while read -r line; do
-    parts=(${line//\// })
-    export GOOS=${parts[0]}
-    export GOARCH=${parts[1]}
-    if go tool compile -V >/dev/null 2>&1 ; then
-        echo Try GOOS=${GOOS} GOARCH=${GOARCH}
-        if ! go install; then
-            echo "*** Failed compiling GOOS=${GOOS} GOARCH=${GOARCH}"
-            failures=$((failures+1))
-        fi
-    else
-        echo Skipping GOOS=${GOOS} GOARCH=${GOARCH} as not supported
-    fi
-done < <(go tool dist list)
-
-if [ $failures -ne 0 ]; then
-    echo "*** $failures compile failures"
-    exit 1
-fi
diff --git a/vendor/github.com/a8m/tree/csort_bsd.go b/vendor/github.com/a8m/tree/csort_bsd.go
deleted file mode 100644
index f5895a62..00000000
--- a/vendor/github.com/a8m/tree/csort_bsd.go
+++ /dev/null
@@ -1,18 +0,0 @@
-//+build darwin freebsd netbsd
-
-package tree
-
-import (
-	"os"
-	"syscall"
-)
-
-func CTimeSort(f1, f2 os.FileInfo) bool {
-	s1, ok1 := f1.Sys().(*syscall.Stat_t)
-	s2, ok2 := f2.Sys().(*syscall.Stat_t)
-	// If this type of node isn't an os node then revert to ModSort
-	if !ok1 || !ok2 {
-		return ModSort(f1, f2)
-	}
-	return s1.Ctimespec.Sec < s2.Ctimespec.Sec
-}
diff --git a/vendor/github.com/a8m/tree/csort_generic.go b/vendor/github.com/a8m/tree/csort_generic.go
deleted file mode 100644
index 38be9aad..00000000
--- a/vendor/github.com/a8m/tree/csort_generic.go
+++ /dev/null
@@ -1,6 +0,0 @@
-//+build !linux,!openbsd,!dragonfly,!android,!solaris,!darwin,!freebsd,!netbsd
-
-package tree
-
-// CtimeSort for unsupported OS - just compare ModTime
-var CTimeSort = ModSort
diff --git a/vendor/github.com/a8m/tree/csort_unix.go b/vendor/github.com/a8m/tree/csort_unix.go
deleted file mode 100644
index 6acd0922..00000000
--- a/vendor/github.com/a8m/tree/csort_unix.go
+++ /dev/null
@@ -1,18 +0,0 @@
-//+build linux openbsd dragonfly android solaris
-
-package tree
-
-import (
-	"os"
-	"syscall"
-)
-
-func CTimeSort(f1, f2 os.FileInfo) bool {
-	s1, ok1 := f1.Sys().(*syscall.Stat_t)
-	s2, ok2 := f2.Sys().(*syscall.Stat_t)
-	// If this type of node isn't an os node then revert to ModSort
-	if !ok1 || !ok2 {
-		return ModSort(f1, f2)
-	}
-	return s1.Ctim.Sec < s2.Ctim.Sec
-}
diff --git a/vendor/github.com/a8m/tree/modes_bsd.go b/vendor/github.com/a8m/tree/modes_bsd.go
deleted file mode 100644
index c9720654..00000000
--- a/vendor/github.com/a8m/tree/modes_bsd.go
+++ /dev/null
@@ -1,7 +0,0 @@
-//+build dragonfly freebsd openbsd solaris windows
-
-package tree
-
-import "syscall"
-
-const modeExecute = syscall.S_IXUSR
diff --git a/vendor/github.com/a8m/tree/modes_unix.go b/vendor/github.com/a8m/tree/modes_unix.go
deleted file mode 100644
index 1394bf6b..00000000
--- a/vendor/github.com/a8m/tree/modes_unix.go
+++ /dev/null
@@ -1,7 +0,0 @@
-//+build android darwin linux nacl netbsd
-
-package tree
-
-import "syscall"
-
-const modeExecute = syscall.S_IXUSR | syscall.S_IXGRP | syscall.S_IXOTH
diff --git a/vendor/github.com/a8m/tree/modes_unsupported.go b/vendor/github.com/a8m/tree/modes_unsupported.go
deleted file mode 100644
index 2fe96cb4..00000000
--- a/vendor/github.com/a8m/tree/modes_unsupported.go
+++ /dev/null
@@ -1,5 +0,0 @@
-//+build !dragonfly,!freebsd,!openbsd,!solaris,!windows,!android,!darwin,!linux,!nacl,!netbsd
-
-package tree
-
-const modeExecute = 0
diff --git a/vendor/github.com/a8m/tree/node.go b/vendor/github.com/a8m/tree/node.go
deleted file mode 100644
index 4a2ef94b..00000000
--- a/vendor/github.com/a8m/tree/node.go
+++ /dev/null
@@ -1,414 +0,0 @@
-package tree
-
-import (
-	"errors"
-	"fmt"
-	"io"
-	"os"
-	"os/user"
-	"path/filepath"
-	"regexp"
-	"sort"
-	"strconv"
-	"strings"
-)
-
-// Node represent some node in the tree
-// contains FileInfo, and its childs
-type Node struct {
-	os.FileInfo
-	path   string
-	depth  int
-	err    error
-	nodes  Nodes
-	vpaths map[string]bool
-}
-
-// List of nodes
-type Nodes []*Node
-
-// To use this package programmatically, you must implement this
-// interface.
-// For example: PTAL on 'cmd/tree/tree.go'
-type Fs interface {
-	Stat(path string) (os.FileInfo, error)
-	ReadDir(path string) ([]string, error)
-}
-
-// Options store the configuration for specific tree.
-// Note, that 'Fs', and 'OutFile' are required (OutFile can be os.Stdout).
-type Options struct {
-	Fs      Fs
-	OutFile io.Writer
-	// List
-	All        bool
-	DirsOnly   bool
-	FullPath   bool
-	IgnoreCase bool
-	FollowLink bool
-	DeepLevel  int
-	Pattern    string
-	IPattern   string
-	// File
-	ByteSize bool
-	UnitSize bool
-	FileMode bool
-	ShowUid  bool
-	ShowGid  bool
-	LastMod  bool
-	Quotes   bool
-	Inodes   bool
-	Device   bool
-	// Sort
-	NoSort    bool
-	VerSort   bool
-	ModSort   bool
-	DirSort   bool
-	NameSort  bool
-	SizeSort  bool
-	CTimeSort bool
-	ReverSort bool
-	// Graphics
-	NoIndent bool
-	Colorize bool
-	// Color defaults to ANSIColor()
-	Color func(*Node, string) string
-}
-
-func (opts *Options) color(node *Node, s string) string {
-	f := opts.Color
-	if f == nil {
-		f = ANSIColor
-	}
-	return f(node, s)
-}
-
-// New get path and create new node(root).
-func New(path string) *Node {
-	return &Node{path: path, vpaths: make(map[string]bool)}
-}
-
-// Visit all files under the given node.
-func (node *Node) Visit(opts *Options) (dirs, files int) {
-	// visited paths
-	if path, err := filepath.Abs(node.path); err == nil {
-		path = filepath.Clean(path)
-		node.vpaths[path] = true
-	}
-	// stat
-	fi, err := opts.Fs.Stat(node.path)
-	if err != nil {
-		node.err = err
-		return
-	}
-	node.FileInfo = fi
-	if !fi.IsDir() {
-		return 0, 1
-	}
-	// increase dirs only if it's a dir, but not the root.
-	if node.depth != 0 {
-		dirs++
-	}
-	// DeepLevel option
-	if opts.DeepLevel > 0 && opts.DeepLevel <= node.depth {
-		return
-	}
-	names, err := opts.Fs.ReadDir(node.path)
-	if err != nil {
-		node.err = err
-		return
-	}
-	node.nodes = make(Nodes, 0)
-	for _, name := range names {
-		// "all" option
-		if !opts.All && strings.HasPrefix(name, ".") {
-			continue
-		}
-		nnode := &Node{
-			path:   filepath.Join(node.path, name),
-			depth:  node.depth + 1,
-			vpaths: node.vpaths,
-		}
-		d, f := nnode.Visit(opts)
-		if nnode.err == nil && !nnode.IsDir() {
-			// "dirs only" option
-			if opts.DirsOnly {
-				continue
-			}
-			var rePrefix string
-			if opts.IgnoreCase {
-				rePrefix = "(?i)"
-			}
-			// Pattern matching
-			if opts.Pattern != "" {
-				re, err := regexp.Compile(rePrefix + opts.Pattern)
-				if err == nil && !re.MatchString(name) {
-					continue
-				}
-			}
-			// IPattern matching
-			if opts.IPattern != "" {
-				re, err := regexp.Compile(rePrefix + opts.IPattern)
-				if err == nil && re.MatchString(name) {
-					continue
-				}
-			}
-		}
-		node.nodes = append(node.nodes, nnode)
-		dirs, files = dirs+d, files+f
-	}
-	// Sorting
-	if !opts.NoSort {
-		node.sort(opts)
-	}
-	return
-}
-
-func (node *Node) sort(opts *Options) {
-	var fn SortFunc
-	switch {
-	case opts.ModSort:
-		fn = ModSort
-	case opts.CTimeSort:
-		fn = CTimeSort
-	case opts.DirSort:
-		fn = DirSort
-	case opts.VerSort:
-		fn = VerSort
-	case opts.SizeSort:
-		fn = SizeSort
-	case opts.NameSort:
-		fn = NameSort
-	default:
-		fn = NameSort // Default should be sorted, not unsorted.
-	}
-	if fn != nil {
-		if opts.ReverSort {
-			sort.Sort(sort.Reverse(ByFunc{node.nodes, fn}))
-		} else {
-			sort.Sort(ByFunc{node.nodes, fn})
-		}
-	}
-}
-
-// Path returns the Node's absolute path
-func (node *Node) Path() string {
-	return node.path
-}
-
-// Print nodes based on the given configuration.
-func (node *Node) Print(opts *Options) { node.print("", opts) }
-
-func dirRecursiveSize(opts *Options, node *Node) (size int64, err error) {
-	if opts.DeepLevel > 0 && node.depth >= opts.DeepLevel {
-		err = errors.New("Depth too high")
-	}
-
-	for _, nnode := range node.nodes {
-		if nnode.err != nil {
-			err = nnode.err
-			continue
-		}
-
-		if !nnode.IsDir() {
-			size += nnode.Size()
-		} else {
-			nsize, e := dirRecursiveSize(opts, nnode)
-			size += nsize
-			if e != nil {
-				err = e
-			}
-		}
-	}
-	return
-}
-
-func (node *Node) print(indent string, opts *Options) {
-	if node.err != nil {
-		err := node.err.Error()
-		if msgs := strings.Split(err, ": "); len(msgs) > 1 {
-			err = msgs[1]
-		}
-		fmt.Printf("%s [%s]\n", node.path, err)
-		return
-	}
-	if !node.IsDir() {
-		var props []string
-		ok, inode, device, uid, gid := getStat(node)
-		// inodes
-		if ok && opts.Inodes {
-			props = append(props, fmt.Sprintf("%d", inode))
-		}
-		// device
-		if ok && opts.Device {
-			props = append(props, fmt.Sprintf("%3d", device))
-		}
-		// Mode
-		if opts.FileMode {
-			props = append(props, node.Mode().String())
-		}
-		// Owner/Uid
-		if ok && opts.ShowUid {
-			uidStr := strconv.Itoa(int(uid))
-			if u, err := user.LookupId(uidStr); err != nil {
-				props = append(props, fmt.Sprintf("%-8s", uidStr))
-			} else {
-				props = append(props, fmt.Sprintf("%-8s", u.Username))
-			}
-		}
-		// Gorup/Gid
-		// TODO: support groupname
-		if ok && opts.ShowGid {
-			gidStr := strconv.Itoa(int(gid))
-			props = append(props, fmt.Sprintf("%-4s", gidStr))
-		}
-		// Size
-		if opts.ByteSize || opts.UnitSize {
-			var size string
-			if opts.UnitSize {
-				size = fmt.Sprintf("%4s", formatBytes(node.Size()))
-			} else {
-				size = fmt.Sprintf("%11d", node.Size())
-			}
-			props = append(props, size)
-		}
-		// Last modification
-		if opts.LastMod {
-			props = append(props, node.ModTime().Format("Jan 02 15:04"))
-		}
-		// Print properties
-		if len(props) > 0 {
-			fmt.Fprintf(opts.OutFile, "[%s]  ", strings.Join(props, " "))
-		}
-	} else {
-		var props []string
-		// Size
-		if opts.ByteSize || opts.UnitSize {
-			var size string
-			rsize, err := dirRecursiveSize(opts, node)
-			if err != nil && rsize <= 0 {
-				if opts.UnitSize {
-					size = "????"
-				} else {
-					size = "???????????"
-				}
-			} else if opts.UnitSize {
-				size = fmt.Sprintf("%4s", formatBytes(rsize))
-			} else {
-				size = fmt.Sprintf("%11d", rsize)
-			}
-			props = append(props, size)
-		}
-		// Print properties
-		if len(props) > 0 {
-			fmt.Fprintf(opts.OutFile, "[%s]  ", strings.Join(props, " "))
-		}
-	}
-	// name/path
-	var name string
-	if node.depth == 0 || opts.FullPath {
-		name = node.path
-	} else {
-		name = node.Name()
-	}
-	// Quotes
-	if opts.Quotes {
-		name = fmt.Sprintf("\"%s\"", name)
-	}
-	// Colorize
-	if opts.Colorize {
-		name = opts.color(node, name)
-	}
-	// IsSymlink
-	if node.Mode()&os.ModeSymlink == os.ModeSymlink {
-		vtarget, err := os.Readlink(node.path)
-		if err != nil {
-			vtarget = node.path
-		}
-		targetPath, err := filepath.EvalSymlinks(node.path)
-		if err != nil {
-			targetPath = vtarget
-		}
-		fi, err := opts.Fs.Stat(targetPath)
-		if opts.Colorize && fi != nil {
-			vtarget = opts.color(&Node{FileInfo: fi, path: vtarget}, vtarget)
-		}
-		name = fmt.Sprintf("%s -> %s", name, vtarget)
-		// Follow symbolic links like directories
-		if opts.FollowLink {
-			path, err := filepath.Abs(targetPath)
-			if err == nil && fi != nil && fi.IsDir() {
-				if _, ok := node.vpaths[filepath.Clean(path)]; !ok {
-					inf := &Node{FileInfo: fi, path: targetPath}
-					inf.vpaths = node.vpaths
-					inf.Visit(opts)
-					node.nodes = inf.nodes
-				} else {
-					name += " [recursive, not followed]"
-				}
-			}
-		}
-	}
-	// Print file details
-	// the main idea of the print logic came from here: github.com/campoy/tools/tree
-	fmt.Fprintln(opts.OutFile, name)
-	add := "│   "
-	for i, nnode := range node.nodes {
-		if opts.NoIndent {
-			add = ""
-		} else {
-			if i == len(node.nodes)-1 {
-				fmt.Fprintf(opts.OutFile, indent+"└── ")
-				add = "    "
-			} else {
-				fmt.Fprintf(opts.OutFile, indent+"├── ")
-			}
-		}
-		nnode.print(indent+add, opts)
-	}
-}
-
-const (
-	_        = iota // ignore first value by assigning to blank identifier
-	KB int64 = 1 << (10 * iota)
-	MB
-	GB
-	TB
-	PB
-	EB
-)
-
-// Convert bytes to human readable string. Like a 2 MB, 64.2 KB, 52 B
-func formatBytes(i int64) (result string) {
-	var n float64
-	sFmt, eFmt := "%.01f", ""
-	switch {
-	case i > EB:
-		eFmt = "E"
-		n = float64(i) / float64(EB)
-	case i > PB:
-		eFmt = "P"
-		n = float64(i) / float64(PB)
-	case i > TB:
-		eFmt = "T"
-		n = float64(i) / float64(TB)
-	case i > GB:
-		eFmt = "G"
-		n = float64(i) / float64(GB)
-	case i > MB:
-		eFmt = "M"
-		n = float64(i) / float64(MB)
-	case i > KB:
-		eFmt = "K"
-		n = float64(i) / float64(KB)
-	default:
-		sFmt = "%.0f"
-		n = float64(i)
-	}
-	if eFmt != "" && n >= 10 {
-		sFmt = "%.0f"
-	}
-	result = fmt.Sprintf(sFmt+eFmt, n)
-	result = strings.Trim(result, " ")
-	return
-}
diff --git a/vendor/github.com/a8m/tree/sort.go b/vendor/github.com/a8m/tree/sort.go
deleted file mode 100644
index 55299935..00000000
--- a/vendor/github.com/a8m/tree/sort.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package tree
-
-import "os"
-
-func (n Nodes) Len() int      { return len(n) }
-func (n Nodes) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
-
-type ByFunc struct {
-	Nodes
-	Fn SortFunc
-}
-
-func (b ByFunc) Less(i, j int) bool {
-	return b.Fn(b.Nodes[i].FileInfo, b.Nodes[j].FileInfo)
-}
-
-type SortFunc func(f1, f2 os.FileInfo) bool
-
-func ModSort(f1, f2 os.FileInfo) bool {
-	return f1.ModTime().Before(f2.ModTime())
-}
-
-func DirSort(f1, f2 os.FileInfo) bool {
-	return f1.IsDir() && !f2.IsDir()
-}
-
-func SizeSort(f1, f2 os.FileInfo) bool {
-	return f1.Size() < f2.Size()
-}
-
-func NameSort(f1, f2 os.FileInfo) bool {
-	return f1.Name() < f2.Name()
-}
-
-func VerSort(f1, f2 os.FileInfo) bool {
-	return NaturalLess(f1.Name(), f2.Name())
-}
-
-func isdigit(b byte) bool { return '0' <= b && b <= '9' }
-
-// NaturalLess compares two strings using natural ordering. This means that e.g.
-// "abc2" < "abc12".
-//
-// Non-digit sequences and numbers are compared separately. The former are
-// compared bytewise, while the latter are compared numerically (except that
-// the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02")
-//
-// Limitation: only ASCII digits (0-9) are considered.
-// Code taken from:
-// https://github.com/fvbommel/util/blob/master/sortorder/natsort.go
-func NaturalLess(str1, str2 string) bool {
-	idx1, idx2 := 0, 0
-	for idx1 < len(str1) && idx2 < len(str2) {
-		c1, c2 := str1[idx1], str2[idx2]
-		dig1, dig2 := isdigit(c1), isdigit(c2)
-		switch {
-		case dig1 != dig2: // Digits before other characters.
-			return dig1 // True if LHS is a digit, false if the RHS is one.
-		case !dig1: // && !dig2, because dig1 == dig2
-			// UTF-8 compares bytewise-lexicographically, no need to decode
-			// codepoints.
-			if c1 != c2 {
-				return c1 < c2
-			}
-			idx1++
-			idx2++
-		default: // Digits
-			// Eat zeros.
-			for ; idx1 < len(str1) && str1[idx1] == '0'; idx1++ {
-			}
-			for ; idx2 < len(str2) && str2[idx2] == '0'; idx2++ {
-			}
-			// Eat all digits.
-			nonZero1, nonZero2 := idx1, idx2
-			for ; idx1 < len(str1) && isdigit(str1[idx1]); idx1++ {
-			}
-			for ; idx2 < len(str2) && isdigit(str2[idx2]); idx2++ {
-			}
-			// If lengths of numbers with non-zero prefix differ, the shorter
-			// one is less.
-			if len1, len2 := idx1-nonZero1, idx2-nonZero2; len1 != len2 {
-				return len1 < len2
-			}
-			// If they're not equal, string comparison is correct.
-			if nr1, nr2 := str1[nonZero1:idx1], str2[nonZero2:idx2]; nr1 != nr2 {
-				return nr1 < nr2
-			}
-			// Otherwise, the one with less zeros is less.
-			// Because everything up to the number is equal, comparing the index
-			// after the zeros is sufficient.
-			if nonZero1 != nonZero2 {
-				return nonZero1 < nonZero2
-			}
-		}
-		// They're identical so far, so continue comparing.
-	}
-	// So far they are identical. At least one is ended. If the other continues,
-	// it sorts last.
-	return len(str1) < len(str2)
-}
diff --git a/vendor/github.com/a8m/tree/stat_unix.go b/vendor/github.com/a8m/tree/stat_unix.go
deleted file mode 100644
index 5ed7b0a4..00000000
--- a/vendor/github.com/a8m/tree/stat_unix.go
+++ /dev/null
@@ -1,20 +0,0 @@
-//+build !plan9,!windows
-
-package tree
-
-import (
-	"os"
-	"syscall"
-)
-
-func getStat(fi os.FileInfo) (ok bool, inode, device, uid, gid uint64) {
-	sys := fi.Sys()
-	if sys == nil {
-		return false, 0, 0, 0, 0
-	}
-	stat, ok := sys.(*syscall.Stat_t)
-	if !ok {
-		return false, 0, 0, 0, 0
-	}
-	return true, uint64(stat.Ino), uint64(stat.Dev), uint64(stat.Uid), uint64(stat.Gid)
-}
diff --git a/vendor/github.com/a8m/tree/stat_unsupported.go b/vendor/github.com/a8m/tree/stat_unsupported.go
deleted file mode 100644
index b37407da..00000000
--- a/vendor/github.com/a8m/tree/stat_unsupported.go
+++ /dev/null
@@ -1,9 +0,0 @@
-//+build plan9 windows
-
-package tree
-
-import "os"
-
-func getStat(fi os.FileInfo) (ok bool, inode, device, uid, gid uint64) {
-	return false, 0, 0, 0, 0
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index eb86d181..8bc5ea17 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1,5 +1,3 @@
-# github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d
-github.com/a8m/tree
 # github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892 => github.com/rasky/go-xdr v0.0.0-20170217172119-4930550ba2e2
 github.com/davecgh/go-xdr/xdr2
 # github.com/google/uuid v0.0.0-20170306145142-6a5e28554805
@@ -9,6 +7,6 @@ github.com/kr/pretty
 # github.com/kr/text v0.1.0
 github.com/kr/text
 # github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728
-github.com/vmware/vmw-guestinfo/bdoor
 github.com/vmware/vmw-guestinfo/message
 github.com/vmware/vmw-guestinfo/vmcheck
+github.com/vmware/vmw-guestinfo/bdoor
diff --git a/view/container_view.go b/view/container_view.go
index c420acf9..37fa34ce 100644
--- a/view/container_view.go
+++ b/view/container_view.go
@@ -36,7 +36,7 @@ func NewContainerView(c *vim25.Client, ref types.ManagedObjectReference) *Contai
 }
 
 // Retrieve populates dst as property.Collector.Retrieve does, for all entities in the view of types specified by kind.
-func (v ContainerView) Retrieve(ctx context.Context, kind []string, ps []string, dst interface{}, pspec ...types.PropertySpec) error {
+func (v ContainerView) Retrieve(ctx context.Context, kind []string, ps []string, dst interface{}) error {
 	pc := property.DefaultCollector(v.Client())
 
 	ospec := types.ObjectSpec{
@@ -50,6 +50,8 @@ func (v ContainerView) Retrieve(ctx context.Context, kind []string, ps []string,
 		},
 	}
 
+	var pspec []types.PropertySpec
+
 	if len(kind) == 0 {
 		kind = []string{"ManagedEntity"}
 	}


Test patch:
diff --git a/govc/test/object.bats b/govc/test/object.bats
index 55dc0b32..01afa328 100755
--- a/govc/test/object.bats
+++ b/govc/test/object.bats
@@ -613,13 +613,3 @@ EOF
   rm -rf "$dir"
   assert_equal 6 "$n"
 }
-
-@test "tree" {
-  vcsim_start -dc 2 -folder 1 -pod 1 -nsx 1 -pool 2
-
-  run govc tree
-  assert_success
-
-  run govc tree /DC0
-  assert_success
-}
diff --git a/govc/test/session.bats b/govc/test/session.bats
index 9c6e5a35..1427b022 100755
--- a/govc/test/session.bats
+++ b/govc/test/session.bats
@@ -13,6 +13,12 @@ load test_helper
 
   # Test User-Agent
   govc session.ls | grep "$(govc version | tr ' ' /)"
+
+  run govc session.ls -S
+  assert_success
+
+  run govc session.ls -u "$(govc env GOVC_USERNAME)@$(govc env GOVC_URL)" -S
+  assert_failure # no password
 }
 
 @test "session.rm" {