github.com/vmware/govmomi@v0.37.1/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 }