github.com/vmware/govmomi@v0.37.2/govc/datastore/ls.go (about)

     1  /*
     2  Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package datastore
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"flag"
    23  	"fmt"
    24  	"io"
    25  	"path"
    26  	"strings"
    27  	"text/tabwriter"
    28  
    29  	"github.com/vmware/govmomi/govc/cli"
    30  	"github.com/vmware/govmomi/govc/flags"
    31  	"github.com/vmware/govmomi/object"
    32  	"github.com/vmware/govmomi/units"
    33  	"github.com/vmware/govmomi/vim25/types"
    34  )
    35  
    36  type ls struct {
    37  	*flags.DatastoreFlag
    38  	*flags.OutputFlag
    39  
    40  	long    bool
    41  	slash   bool
    42  	all     bool
    43  	recurse bool
    44  }
    45  
    46  func init() {
    47  	cli.Register("datastore.ls", &ls{})
    48  }
    49  
    50  func (cmd *ls) Register(ctx context.Context, f *flag.FlagSet) {
    51  	cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx)
    52  	cmd.DatastoreFlag.Register(ctx, f)
    53  
    54  	cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx)
    55  	cmd.OutputFlag.Register(ctx, f)
    56  
    57  	f.BoolVar(&cmd.long, "l", false, "Long listing format")
    58  	f.BoolVar(&cmd.slash, "p", false, "Append / indicator to directories")
    59  	f.BoolVar(&cmd.all, "a", false, "Do not ignore entries starting with .")
    60  	f.BoolVar(&cmd.recurse, "R", false, "List subdirectories recursively")
    61  }
    62  
    63  func (cmd *ls) Process(ctx context.Context) error {
    64  	if err := cmd.DatastoreFlag.Process(ctx); err != nil {
    65  		return err
    66  	}
    67  	if err := cmd.OutputFlag.Process(ctx); err != nil {
    68  		return err
    69  	}
    70  	return nil
    71  }
    72  
    73  func (cmd *ls) Usage() string {
    74  	return "[FILE]..."
    75  }
    76  
    77  func isInvalid(err error) bool {
    78  	if f, ok := err.(types.HasFault); ok {
    79  		switch f.Fault().(type) {
    80  		case *types.InvalidArgument:
    81  			return true
    82  		}
    83  	}
    84  
    85  	return false
    86  }
    87  
    88  func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error {
    89  	args := cmd.Args(f.Args())
    90  
    91  	ds, err := cmd.Datastore()
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	b, err := ds.Browser(ctx)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	if len(args) == 0 {
   102  		args = append(args, object.DatastorePath{})
   103  	}
   104  
   105  	result := &listOutput{
   106  		rs:  make([]types.HostDatastoreBrowserSearchResults, 0),
   107  		cmd: cmd,
   108  	}
   109  
   110  	for _, p := range args {
   111  		arg := p.Path
   112  
   113  		spec := types.HostDatastoreBrowserSearchSpec{
   114  			MatchPattern: []string{"*"},
   115  		}
   116  
   117  		if cmd.long {
   118  			spec.Details = &types.FileQueryFlags{
   119  				FileType:     true,
   120  				FileSize:     true,
   121  				FileOwner:    types.NewBool(true), // TODO: omitempty is generated, but seems to be required
   122  				Modification: true,
   123  			}
   124  		}
   125  
   126  		for i := 0; ; i++ {
   127  			r, err := cmd.ListPath(b, arg, spec)
   128  			if err != nil {
   129  				// Treat the argument as a match pattern if not found as directory
   130  				if i == 0 && types.IsFileNotFound(err) || isInvalid(err) {
   131  					spec.MatchPattern[0] = path.Base(arg)
   132  					arg = path.Dir(arg)
   133  					continue
   134  				}
   135  
   136  				return err
   137  			}
   138  
   139  			// Treat an empty result against match pattern as file not found
   140  			if i == 1 && len(r) == 1 && len(r[0].File) == 0 {
   141  				return fmt.Errorf("file %s/%s was not found", r[0].FolderPath, spec.MatchPattern[0])
   142  			}
   143  
   144  			for n := range r {
   145  				result.add(r[n])
   146  			}
   147  
   148  			break
   149  		}
   150  	}
   151  
   152  	return cmd.WriteResult(result)
   153  }
   154  
   155  func (cmd *ls) ListPath(b *object.HostDatastoreBrowser, path string, spec types.HostDatastoreBrowserSearchSpec) ([]types.HostDatastoreBrowserSearchResults, error) {
   156  	ctx := context.TODO()
   157  
   158  	path, err := cmd.DatastorePath(path)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	search := b.SearchDatastore
   164  	if cmd.recurse {
   165  		search = b.SearchDatastoreSubFolders
   166  	}
   167  
   168  	task, err := search(ctx, path, &spec)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	info, err := task.WaitForResult(ctx, nil)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  
   178  	switch r := info.Result.(type) {
   179  	case types.HostDatastoreBrowserSearchResults:
   180  		return []types.HostDatastoreBrowserSearchResults{r}, nil
   181  	case types.ArrayOfHostDatastoreBrowserSearchResults:
   182  		return r.HostDatastoreBrowserSearchResults, nil
   183  	default:
   184  		panic(fmt.Sprintf("unknown result type: %T", r))
   185  	}
   186  }
   187  
   188  type listOutput struct {
   189  	rs  []types.HostDatastoreBrowserSearchResults
   190  	cmd *ls
   191  }
   192  
   193  func (o *listOutput) add(r types.HostDatastoreBrowserSearchResults) {
   194  	if o.cmd.recurse && !o.cmd.all {
   195  		// filter out ".hidden" directories
   196  		path := strings.SplitN(r.FolderPath, " ", 2)
   197  		if len(path) == 2 {
   198  			path = strings.Split(path[1], "/")
   199  			if path[0] == "." {
   200  				path = path[1:]
   201  			}
   202  
   203  			for _, p := range path {
   204  				if p != "" && p[0] == '.' {
   205  					return
   206  				}
   207  			}
   208  		}
   209  	}
   210  
   211  	res := r
   212  	res.File = nil
   213  
   214  	for _, f := range r.File {
   215  		if f.GetFileInfo().Path[0] == '.' && !o.cmd.all {
   216  			continue
   217  		}
   218  
   219  		if o.cmd.slash {
   220  			if d, ok := f.(*types.FolderFileInfo); ok {
   221  				d.Path += "/"
   222  			}
   223  		}
   224  
   225  		res.File = append(res.File, f)
   226  	}
   227  
   228  	o.rs = append(o.rs, res)
   229  }
   230  
   231  // hasMultiplePaths returns whether or not the slice of search results contains
   232  // results from more than one folder path.
   233  func (o *listOutput) hasMultiplePaths() bool {
   234  	if len(o.rs) == 0 {
   235  		return false
   236  	}
   237  
   238  	p := o.rs[0].FolderPath
   239  
   240  	// Multiple paths if any entry is not equal to the first one.
   241  	for _, e := range o.rs {
   242  		if e.FolderPath != p {
   243  			return true
   244  		}
   245  	}
   246  
   247  	return false
   248  }
   249  
   250  func (o *listOutput) MarshalJSON() ([]byte, error) {
   251  	return json.Marshal(o.rs)
   252  }
   253  
   254  func (o *listOutput) Write(w io.Writer) error {
   255  	// Only include path header if we're dealing with more than one path.
   256  	includeHeader := false
   257  	if o.hasMultiplePaths() {
   258  		includeHeader = true
   259  	}
   260  
   261  	tw := tabwriter.NewWriter(w, 3, 0, 2, ' ', 0)
   262  	for i, r := range o.rs {
   263  		if includeHeader {
   264  			if i > 0 {
   265  				fmt.Fprintf(tw, "\n")
   266  			}
   267  			fmt.Fprintf(tw, "%s:\n", r.FolderPath)
   268  		}
   269  		for _, file := range r.File {
   270  			info := file.GetFileInfo()
   271  			if o.cmd.long {
   272  				fmt.Fprintf(tw, "%s\t%s\t%s\n", units.ByteSize(info.FileSize), info.Modification.Format("Mon Jan 2 15:04:05 2006"), info.Path)
   273  			} else {
   274  				fmt.Fprintf(tw, "%s\n", info.Path)
   275  			}
   276  		}
   277  	}
   278  	tw.Flush()
   279  	return nil
   280  }