github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/mv-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  	"fmt"
    23  	"sync"
    24  
    25  	"github.com/fatih/color"
    26  	"github.com/minio/cli"
    27  	"github.com/minio/mc/pkg/probe"
    28  	"github.com/minio/pkg/v2/console"
    29  )
    30  
    31  // mv command flags.
    32  var (
    33  	mvFlags = []cli.Flag{
    34  		cli.BoolFlag{
    35  			Name:  "recursive, r",
    36  			Usage: "move recursively",
    37  		},
    38  		cli.StringFlag{
    39  			Name:  "older-than",
    40  			Usage: "move objects older than value in duration string (e.g. 7d10h31s)",
    41  		},
    42  		cli.StringFlag{
    43  			Name:  "newer-than",
    44  			Usage: "move objects newer than value in duration string (e.g. 7d10h31s)",
    45  		},
    46  		cli.StringFlag{
    47  			Name:  "storage-class, sc",
    48  			Usage: "set storage class for new object(s) on target",
    49  		},
    50  		cli.StringFlag{
    51  			Name:  "attr",
    52  			Usage: "add custom metadata for the object",
    53  		},
    54  		cli.BoolFlag{
    55  			Name:  "preserve, a",
    56  			Usage: "preserve filesystem attributes (mode, ownership, timestamps)",
    57  		},
    58  		cli.BoolFlag{
    59  			Name:  "disable-multipart",
    60  			Usage: "disable multipart upload feature",
    61  		},
    62  	}
    63  )
    64  
    65  // Move command.
    66  var mvCmd = cli.Command{
    67  	Name:         "mv",
    68  	Usage:        "move objects",
    69  	Action:       mainMove,
    70  	OnUsageError: onUsageError,
    71  	Before:       setGlobalsFromContext,
    72  	Flags:        append(append(mvFlags, encFlags...), globalFlags...),
    73  	CustomHelpTemplate: `NAME:
    74    {{.HelpName}} - {{.Usage}}
    75  
    76  USAGE:
    77    {{.HelpName}} [FLAGS] SOURCE [SOURCE...] TARGET
    78  
    79  FLAGS:
    80    {{range .VisibleFlags}}{{.}}
    81    {{end}}
    82  
    83  ENVIRONMENT VARIABLES:
    84    MC_ENC_KMS: KMS encryption key in the form of (alias/prefix=key).
    85    MC_ENC_S3: S3 encryption key in the form of (alias/prefix=key).
    86  
    87  EXAMPLES:
    88    01. Move a list of objects from local file system to Amazon S3 cloud storage.
    89        {{.Prompt}} {{.HelpName}} Music/*.ogg s3/jukebox/
    90  
    91    02. Move a folder recursively from MinIO cloud storage to Amazon S3 cloud storage.
    92        {{.Prompt}} {{.HelpName}} --recursive play/mybucket/ s3/mybucket/
    93  
    94    03. Move multiple local folders recursively to MinIO cloud storage.
    95        {{.Prompt}} {{.HelpName}} --recursive backup/2014/ backup/2015/ play/archive/
    96  
    97    04. Move a bucket recursively from aliased Amazon S3 cloud storage to local filesystem on Windows.
    98        {{.Prompt}} {{.HelpName}} --recursive s3\documents\2014\ C:\Backups\2014
    99  
   100    05. Move files older than 7 days and 10 hours from MinIO cloud storage to Amazon S3 cloud storage.
   101        {{.Prompt}} {{.HelpName}} --older-than 7d10h play/mybucket/myfolder/ s3/mybucket/
   102  
   103    06. Move files newer than 7 days and 10 hours from MinIO cloud storage to a local path.
   104        {{.Prompt}} {{.HelpName}} --newer-than 7d10h play/mybucket/myfolder/ ~/latest/
   105  
   106    07. Move an object with name containing unicode characters to Amazon S3 cloud storage.
   107        {{.Prompt}} {{.HelpName}} 本語 s3/andoria/
   108  
   109    08. Move a local folder with space separated characters to Amazon S3 cloud storage.
   110        {{.Prompt}} {{.HelpName}} --recursive 'workdir/documents/May 2014/' s3/miniocloud
   111  
   112    09. Move a list of objects from local file system to MinIO cloud storage with specified metadata, separated by ";"
   113        {{.Prompt}} {{.HelpName}} --attr "key1=value1;key2=value2" Music/*.mp4 play/mybucket/
   114  
   115    10. Move a folder recursively from MinIO cloud storage to Amazon S3 cloud storage with Cache-Control and custom metadata, separated by ";".
   116        {{.Prompt}} {{.HelpName}} --attr "Cache-Control=max-age=90000,min-fresh=9000;key1=value1;key2=value2" --recursive play/mybucket/myfolder/ s3/mybucket/
   117  
   118    11. Move a text file to an object storage and assign REDUCED_REDUNDANCY storage-class to the uploaded object.
   119        {{.Prompt}} {{.HelpName}} --storage-class REDUCED_REDUNDANCY myobject.txt play/mybucket
   120  
   121    12. Move a text file to an object storage and preserve the file system attribute as metadata.
   122        {{.Prompt}} {{.HelpName}} -a myobject.txt play/mybucket
   123  
   124    13. Move a text file to an object storage and disable multipart upload feature.
   125        {{.Prompt}} {{.HelpName}} --disable-multipart myobject.txt play/mybucket
   126  
   127    14. Move a folder using client provided encryption keys from Amazon S3 to MinIO cloud storage.
   128        {{.Prompt}} {{.HelpName}} --r --enc-c "s3/documents/=MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MBB" --enc-c "myminio/documents/=MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDA" s3/documents/ myminio/documents/
   129  
   130    15. Move a folder using specific server managed encryption keys from Amazon S3 to MinIO cloud storage.
   131        {{.Prompt}} {{.HelpName}} --r --enc-s3 "s3/documents/=my-s3-key" --enc-s3 "myminio/documents/=my-minio-key" s3/documents/ myminio/documents/
   132  `,
   133  }
   134  
   135  type removeClientInfo struct {
   136  	client    Client
   137  	contentCh chan *ClientContent
   138  	resultCh  <-chan RemoveResult
   139  }
   140  
   141  type removeManager struct {
   142  	removeMap      map[string]*removeClientInfo
   143  	removeMapMutex sync.RWMutex
   144  	wg             sync.WaitGroup
   145  }
   146  
   147  func (rm *removeManager) readErrors(resultCh <-chan RemoveResult, targetURL string) {
   148  	rm.wg.Add(1)
   149  	go func() {
   150  		defer rm.wg.Done()
   151  		for result := range resultCh {
   152  			if result.Err != nil {
   153  				errorIf(result.Err.Trace(targetURL), "Failed to remove in`"+targetURL+"`.")
   154  			}
   155  		}
   156  	}()
   157  }
   158  
   159  // This function should be parallel-safe because it is executed by ParallelManager
   160  // If targetAlias is empty, it means we will target local FS contents
   161  func (rm *removeManager) add(ctx context.Context, targetAlias, targetURL string) {
   162  	rm.removeMapMutex.Lock()
   163  	clientInfo := rm.removeMap[targetAlias]
   164  	if clientInfo == nil {
   165  		client, pErr := newClientFromAlias(targetAlias, targetURL)
   166  		if pErr != nil {
   167  			errorIf(pErr.Trace(targetURL), "Invalid argument `"+targetURL+"`.")
   168  			return
   169  		}
   170  
   171  		contentCh := make(chan *ClientContent, 10000)
   172  		resultCh := client.Remove(ctx, false, false, false, false, contentCh)
   173  		rm.readErrors(resultCh, targetURL)
   174  
   175  		clientInfo = &removeClientInfo{
   176  			client:    client,
   177  			contentCh: contentCh,
   178  			resultCh:  resultCh,
   179  		}
   180  
   181  		rm.removeMap[targetAlias] = clientInfo
   182  	}
   183  	rm.removeMapMutex.Unlock()
   184  
   185  	clientInfo.contentCh <- &ClientContent{URL: *newClientURL(targetURL)}
   186  }
   187  
   188  func (rm *removeManager) close() {
   189  	for _, clientInfo := range rm.removeMap {
   190  		close(clientInfo.contentCh)
   191  	}
   192  
   193  	// Wait until all on-going client.Remove() operations to finish
   194  	rm.wg.Wait()
   195  }
   196  
   197  var rmManager = &removeManager{
   198  	removeMap: make(map[string]*removeClientInfo),
   199  }
   200  
   201  // mainMove is the entry point for mv command.
   202  func mainMove(cliCtx *cli.Context) error {
   203  	ctx, cancelMove := context.WithCancel(globalContext)
   204  	defer cancelMove()
   205  
   206  	checkCopySyntax(cliCtx)
   207  	console.SetColor("Copy", color.New(color.FgGreen, color.Bold))
   208  
   209  	if cliCtx.NArg() == 2 {
   210  		args := cliCtx.Args()
   211  		srcURL := args.Get(0)
   212  		dstURL := args.Get(1)
   213  		if srcURL == dstURL {
   214  			fatalIf(errDummy().Trace(), fmt.Sprintf("Source and destination urls cannot be the same: %v.", srcURL))
   215  		}
   216  	}
   217  
   218  	var err *probe.Error
   219  
   220  	encKeyDB, err := validateAndCreateEncryptionKeys(cliCtx)
   221  	fatalIf(err, "Unable to parse encryption keys.")
   222  
   223  	e := doCopySession(ctx, cancelMove, cliCtx, encKeyDB, true)
   224  
   225  	console.Colorize("Copy", "Waiting for move operations to complete")
   226  	rmManager.close()
   227  
   228  	return e
   229  }