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 }