github.com/vmware/govmomi@v0.51.0/cli/datastore/ls.go (about)

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