github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/put-url.go (about)

     1  // Copyright (c) 2015-2024 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  	"strings"
    24  
    25  	"github.com/minio/mc/pkg/probe"
    26  	"github.com/minio/minio-go/v7"
    27  )
    28  
    29  // preparePutURLs - prepares target and source clientURLs for copying.
    30  func preparePutURLs(ctx context.Context, o prepareCopyURLsOpts) chan URLs {
    31  	copyURLsCh := make(chan URLs)
    32  	go func(o prepareCopyURLsOpts) {
    33  		defer close(copyURLsCh)
    34  		copyURLsContent, err := guessPutURLType(ctx, o)
    35  		if err != nil {
    36  			copyURLsCh <- URLs{Error: err}
    37  			return
    38  		}
    39  
    40  		switch copyURLsContent.copyType {
    41  		case copyURLsTypeA:
    42  			copyURLsCh <- prepareCopyURLsTypeA(ctx, *copyURLsContent, o)
    43  		case copyURLsTypeB:
    44  			copyURLsCh <- prepareCopyURLsTypeB(ctx, *copyURLsContent, o)
    45  		default:
    46  			copyURLsCh <- URLs{Error: errInvalidArgument().Trace(o.sourceURLs...)}
    47  		}
    48  	}(o)
    49  
    50  	finalCopyURLsCh := make(chan URLs)
    51  	go func() {
    52  		defer close(finalCopyURLsCh)
    53  		for cpURLs := range copyURLsCh {
    54  			if cpURLs.Error != nil {
    55  				finalCopyURLsCh <- cpURLs
    56  				continue
    57  			}
    58  			finalCopyURLsCh <- cpURLs
    59  		}
    60  	}()
    61  
    62  	return finalCopyURLsCh
    63  }
    64  
    65  // guessPutURLType guesses the type of clientURL. This approach all allows prepareURL
    66  // functions to accurately report failure causes.
    67  func guessPutURLType(ctx context.Context, o prepareCopyURLsOpts) (*copyURLsContent, *probe.Error) {
    68  	cc := new(copyURLsContent)
    69  
    70  	// Extract alias before fiddling with the clientURL.
    71  	cc.sourceURL = o.sourceURLs[0]
    72  	cc.sourceAlias, _, _ = mustExpandAlias(cc.sourceURL)
    73  	// Find alias and expanded clientURL.
    74  	cc.targetAlias, cc.targetURL, _ = mustExpandAlias(o.targetURL)
    75  
    76  	if len(o.sourceURLs) == 1 { // 1 Source, 1 Target
    77  		var err *probe.Error
    78  		var client Client
    79  		client, cc.sourceContent, err = url2Stat(ctx, url2StatOptions{urlStr: cc.sourceURL, versionID: o.versionID, fileAttr: false, encKeyDB: o.encKeyDB, timeRef: o.timeRef, isZip: o.isZip, ignoreBucketExistsCheck: false})
    80  		if err != nil {
    81  			cc.copyType = copyURLsTypeInvalid
    82  			return cc, err
    83  		}
    84  		_, ok := client.(*fsClient)
    85  		if !ok {
    86  			cc.copyType = copyURLsTypeInvalid
    87  			return cc, probe.NewError(fmt.Errorf("Source is not local filepath."))
    88  		}
    89  		// If recursion is ON, it is type C.
    90  		// If source is a folder, it is Type C.
    91  		if cc.sourceContent.Type.IsDir() {
    92  			cc.copyType = copyURLsTypeC
    93  			return cc, nil
    94  		}
    95  		client, err = newClient(o.targetURL)
    96  		if err != nil {
    97  			cc.copyType = copyURLsTypeInvalid
    98  			return cc, err
    99  		}
   100  		s3clnt, ok := client.(*S3Client)
   101  		if !ok {
   102  			cc.copyType = copyURLsTypeInvalid
   103  			return cc, probe.NewError(fmt.Errorf("Target is not s3."))
   104  		}
   105  		bucket, path := s3clnt.url2BucketAndObject()
   106  		if bucket == "" {
   107  			cc.copyType = copyURLsTypeInvalid
   108  			return cc, probe.NewError(fmt.Errorf("Bucket should not be empty."))
   109  		}
   110  		cc.targetContent = s3clnt.objectInfo2ClientContent(bucket, minio.ObjectInfo{
   111  			Key: bucket,
   112  		})
   113  		// If target is a folder, it is Type B.
   114  		var isDir bool
   115  		if path == "" {
   116  			isDir = true
   117  		} else {
   118  			isDir = strings.HasSuffix(path, string(cc.targetContent.URL.Separator))
   119  		}
   120  
   121  		if isDir {
   122  			cc.copyType = copyURLsTypeB
   123  			cc.sourceVersionID = cc.sourceContent.VersionID
   124  			return cc, nil
   125  		}
   126  
   127  		// else Type A.
   128  		cc.copyType = copyURLsTypeA
   129  		cc.sourceVersionID = cc.sourceContent.VersionID
   130  		return cc, nil
   131  	}
   132  
   133  	cc.copyType = copyURLsTypeInvalid
   134  	return cc, errInvalidArgument().Trace()
   135  }