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  }