github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/ls-main.go (about) 1 // Copyright (c) 2015-2022 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "errors" 23 "strings" 24 "time" 25 26 "github.com/fatih/color" 27 "github.com/minio/cli" 28 "github.com/minio/mc/pkg/probe" 29 "github.com/minio/pkg/v2/console" 30 ) 31 32 // ls specific flags. 33 var ( 34 lsFlags = []cli.Flag{ 35 cli.StringFlag{ 36 Name: "rewind", 37 Usage: "list all object versions no later than specified date", 38 }, 39 cli.BoolFlag{ 40 Name: "versions", 41 Usage: "list all versions", 42 }, 43 cli.BoolFlag{ 44 Name: "recursive, r", 45 Usage: "list recursively", 46 }, 47 cli.BoolFlag{ 48 Name: "incomplete, I", 49 Usage: "list incomplete uploads", 50 }, 51 cli.BoolFlag{ 52 Name: "summarize", 53 Usage: "display summary information (number of objects, total size)", 54 }, 55 cli.StringFlag{ 56 Name: "storage-class, sc", 57 Usage: "filter to specified storage class", 58 }, 59 cli.BoolFlag{ 60 Name: "zip", 61 Usage: "list files inside zip archive (MinIO servers only)", 62 }, 63 } 64 ) 65 66 // list files and folders. 67 var lsCmd = cli.Command{ 68 Name: "ls", 69 Usage: "list buckets and objects", 70 Action: mainList, 71 OnUsageError: onUsageError, 72 Before: setGlobalsFromContext, 73 Flags: append(lsFlags, globalFlags...), 74 CustomHelpTemplate: `NAME: 75 {{.HelpName}} - {{.Usage}} 76 77 USAGE: 78 {{.HelpName}} [FLAGS] TARGET [TARGET ...] 79 80 FLAGS: 81 {{range .VisibleFlags}}{{.}} 82 {{end}} 83 EXAMPLES: 84 1. List buckets on Amazon S3 cloud storage. 85 {{.Prompt}} {{.HelpName}} s3 86 87 2. List buckets and all its contents from Amazon S3 cloud storage recursively. 88 {{.Prompt}} {{.HelpName}} --recursive s3 89 90 3. List all contents of mybucket on Amazon S3 cloud storage. 91 {{.Prompt}} {{.HelpName}} s3/mybucket/ 92 93 4. List all contents of mybucket on Amazon S3 cloud storage on Microsoft Windows. 94 {{.Prompt}} {{.HelpName}} s3\mybucket\ 95 96 5. List files recursively on a local filesystem on Microsoft Windows. 97 {{.Prompt}} {{.HelpName}} --recursive C:\Users\Worf\ 98 99 6. List incomplete (previously failed) uploads of objects on Amazon S3. 100 {{.Prompt}} {{.HelpName}} --incomplete s3/mybucket 101 102 7. List contents at a specific time in the past if the bucket versioning is enabled. 103 {{.Prompt}} {{.HelpName}} --rewind 2020.01.01 s3/mybucket 104 {{.Prompt}} {{.HelpName}} --rewind 2020.01.01T11:30 s3/mybucket 105 {{.Prompt}} {{.HelpName}} --rewind 7d s3/mybucket 106 107 8. List all contents versions if the bucket versioning is enabled. 108 {{.Prompt}} {{.HelpName}} --versions s3/mybucket 109 110 9. List all objects on mybucket, summarize the number of objects and total size. 111 {{.Prompt}} {{.HelpName}} --summarize s3/mybucket/ 112 113 10. List all objects on mybucket, for the GLACIER storage class 114 {{.Prompt}} {{.HelpName}} --storage-class 'GLACIER' s3/mybucket 115 `, 116 } 117 118 var rewindSupportedFormat = []string{ 119 "2006.01.02", 120 "2006.01.02T15:04", 121 "2006.01.02T15:04:05", 122 time.RFC3339, 123 } 124 125 // Parse rewind flag while considering the system local time zone 126 func parseRewindFlag(rewind string) (timeRef time.Time) { 127 if rewind != "" { 128 location, e := time.LoadLocation("Local") 129 if e != nil { 130 return 131 } 132 133 for _, format := range rewindSupportedFormat { 134 if t, e := time.ParseInLocation(format, rewind, location); e == nil { 135 timeRef = t 136 break 137 } 138 } 139 140 if timeRef.IsZero() { 141 // rewind is not parsed, check if it is a duration instead 142 if duration, e := ParseDuration(rewind); e == nil { 143 if duration < 0 { 144 fatalIf(probe.NewError(errors.New("negative duration is not supported")), 145 "Unable to parse --rewind argument") 146 } 147 timeRef = time.Now().Add(-time.Duration(duration)) 148 } 149 } 150 151 if timeRef.IsZero() { 152 // rewind argument still not parsed, error out 153 fatalIf(probe.NewError(errors.New("unknown format")), "Unable to parse --rewind argument") 154 } 155 } 156 return 157 } 158 159 // checkListSyntax - validate all the passed arguments 160 func checkListSyntax(cliCtx *cli.Context) ([]string, doListOptions) { 161 args := cliCtx.Args() 162 if !cliCtx.Args().Present() { 163 args = []string{"."} 164 } 165 for _, arg := range args { 166 if strings.TrimSpace(arg) == "" { 167 fatalIf(errInvalidArgument().Trace(args...), "Unable to validate empty argument.") 168 } 169 } 170 171 isRecursive := cliCtx.Bool("recursive") 172 isIncomplete := cliCtx.Bool("incomplete") 173 withOlderVersions := cliCtx.Bool("versions") 174 isSummary := cliCtx.Bool("summarize") 175 listZip := cliCtx.Bool("zip") 176 177 timeRef := parseRewindFlag(cliCtx.String("rewind")) 178 179 if listZip && (withOlderVersions || !timeRef.IsZero()) { 180 fatalIf(errInvalidArgument().Trace(args...), "Zip file listing can only be performed on the latest version") 181 } 182 storageClasss := cliCtx.String("storage-class") 183 opts := doListOptions{ 184 timeRef: timeRef, 185 isRecursive: isRecursive, 186 isIncomplete: isIncomplete, 187 isSummary: isSummary, 188 withOlderVersions: withOlderVersions, 189 listZip: listZip, 190 filter: storageClasss, 191 } 192 return args, opts 193 } 194 195 // mainList - is a handler for mc ls command 196 func mainList(cliCtx *cli.Context) error { 197 ctx, cancelList := context.WithCancel(globalContext) 198 defer cancelList() 199 200 // Additional command specific theme customization. 201 console.SetColor("File", color.New(color.Bold)) 202 console.SetColor("DEL", color.New(color.FgRed)) 203 console.SetColor("PUT", color.New(color.FgGreen)) 204 console.SetColor("VersionID", color.New(color.FgHiBlue)) 205 console.SetColor("VersionOrd", color.New(color.FgHiMagenta)) 206 console.SetColor("Dir", color.New(color.FgCyan, color.Bold)) 207 console.SetColor("Size", color.New(color.FgYellow)) 208 console.SetColor("Time", color.New(color.FgGreen)) 209 console.SetColor("Summarize", color.New(color.Bold)) 210 console.SetColor("SC", color.New(color.FgBlue)) 211 212 // check 'ls' cliCtx arguments. 213 args, opts := checkListSyntax(cliCtx) 214 215 var cErr error 216 for _, targetURL := range args { 217 clnt, err := newClient(targetURL) 218 fatalIf(err.Trace(targetURL), "Unable to initialize target `"+targetURL+"`.") 219 if !strings.HasSuffix(targetURL, string(clnt.GetURL().Separator)) { 220 var st *ClientContent 221 st, err = clnt.Stat(ctx, StatOptions{incomplete: opts.isIncomplete}) 222 if st != nil && err == nil && st.Type.IsDir() { 223 targetURL = targetURL + string(clnt.GetURL().Separator) 224 clnt, err = newClient(targetURL) 225 fatalIf(err.Trace(targetURL), "Unable to initialize target `"+targetURL+"`.") 226 } 227 } 228 if e := doList(ctx, clnt, opts); e != nil { 229 cErr = e 230 } 231 } 232 return cErr 233 }