github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/put-main.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  	"strconv"
    23  	"strings"
    24  
    25  	"github.com/dustin/go-humanize"
    26  	"github.com/minio/cli"
    27  	"github.com/minio/mc/pkg/probe"
    28  	"github.com/minio/pkg/v2/console"
    29  )
    30  
    31  // put command flags.
    32  var (
    33  	putFlags = []cli.Flag{
    34  		cli.IntFlag{
    35  			Name:  "parallel, P",
    36  			Usage: "upload number of parts in parallel",
    37  			Value: 4,
    38  		},
    39  		cli.StringFlag{
    40  			Name:  "part-size, s",
    41  			Usage: "each part size",
    42  			Value: "16MiB",
    43  		},
    44  	}
    45  )
    46  
    47  // Put command.
    48  var putCmd = cli.Command{
    49  	Name:         "put",
    50  	Usage:        "upload an object to a bucket",
    51  	Action:       mainPut,
    52  	OnUsageError: onUsageError,
    53  	Before:       setGlobalsFromContext,
    54  	Flags:        append(append(encFlags, globalFlags...), putFlags...),
    55  	CustomHelpTemplate: `NAME:
    56    {{.HelpName}} - {{.Usage}}
    57  
    58  USAGE:
    59    {{.HelpName}} [FLAGS] SOURCE TARGET
    60  
    61  FLAGS:
    62    {{range .VisibleFlags}}{{.}}
    63    {{end}}
    64  
    65  ENVIRONMENT VARIABLES:
    66    MC_ENC_KMS: KMS encryption key in the form of (alias/prefix=key).
    67    MC_ENC_S3: S3 encryption key in the form of (alias/prefix=key).
    68  
    69  EXAMPLES:
    70    1. Put an object from local file system to S3 storage
    71  		{{.Prompt}} {{.HelpName}} path-to/object play/mybucket
    72  
    73    2. Put an object from local file system to S3 bucket with name
    74  		{{.Prompt}} {{.HelpName}} path-to/object play/mybucket/object
    75  
    76    3. Put an object from local file system to S3 bucket under a prefix
    77  		{{.Prompt}} {{.HelpName}} path-to/object play/mybucket/object-prefix/
    78  
    79  	4. Put an object to MinIO storage using sse-c encryption
    80  		{{.Prompt}} {{.HelpName}} --enc-c "play/mybucket/object=MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDA" path-to/object play/mybucket/object 
    81  
    82  	5. Put an object to MinIO storage using sse-kms encryption
    83  		{{.Prompt}} {{.HelpName}} --enc-kms path-to/object play/mybucket/object 
    84  `,
    85  }
    86  
    87  // mainPut is the entry point for put command.
    88  func mainPut(cliCtx *cli.Context) (e error) {
    89  	args := cliCtx.Args()
    90  	if len(args) < 2 {
    91  		showCommandHelpAndExit(cliCtx, 1) // last argument is exit code.
    92  	}
    93  
    94  	ctx, cancelPut := context.WithCancel(globalContext)
    95  	defer cancelPut()
    96  	// part size
    97  	size := cliCtx.String("s")
    98  	if size == "" {
    99  		size = "16mb"
   100  	}
   101  	_, perr := humanize.ParseBytes(size)
   102  	if perr != nil {
   103  		fatalIf(probe.NewError(perr), "Unable to parse part size")
   104  	}
   105  	// threads
   106  	threads := cliCtx.Int("P")
   107  	if threads < 1 {
   108  		fatalIf(errInvalidArgument().Trace(strconv.Itoa(threads)), "Invalid number of threads")
   109  	}
   110  
   111  	// Parse encryption keys per command.
   112  	encryptionKeys, err := validateAndCreateEncryptionKeys(cliCtx)
   113  	if err != nil {
   114  		err.Trace(cliCtx.Args()...)
   115  	}
   116  	fatalIf(err, "SSE Error")
   117  
   118  	if len(args) < 2 {
   119  		fatalIf(errInvalidArgument().Trace(args...), "Invalid number of arguments.")
   120  	}
   121  	// get source and target
   122  	sourceURLs := args[:len(args)-1]
   123  	targetURL := args[len(args)-1]
   124  
   125  	putURLsCh := make(chan URLs, 10000)
   126  	var totalObjects, totalBytes int64
   127  
   128  	// Store a progress bar or an accounter
   129  	var pg ProgressReader
   130  
   131  	// Enable progress bar reader only during default mode.
   132  	if !globalQuiet && !globalJSON { // set up progress bar
   133  		pg = newProgressBar(totalBytes)
   134  	} else {
   135  		pg = newAccounter(totalBytes)
   136  	}
   137  	go func() {
   138  		opts := prepareCopyURLsOpts{
   139  			sourceURLs:              sourceURLs,
   140  			targetURL:               targetURL,
   141  			encKeyDB:                encryptionKeys,
   142  			ignoreBucketExistsCheck: true,
   143  		}
   144  
   145  		for putURLs := range preparePutURLs(ctx, opts) {
   146  			if putURLs.Error != nil {
   147  				putURLsCh <- putURLs
   148  				break
   149  			}
   150  			totalBytes += putURLs.SourceContent.Size
   151  			pg.SetTotal(totalBytes)
   152  			totalObjects++
   153  			putURLsCh <- putURLs
   154  		}
   155  		close(putURLsCh)
   156  	}()
   157  	for {
   158  		select {
   159  		case <-ctx.Done():
   160  			showLastProgressBar(pg, nil)
   161  			return
   162  		case putURLs, ok := <-putURLsCh:
   163  			if !ok {
   164  				showLastProgressBar(pg, nil)
   165  				return
   166  			}
   167  			if putURLs.Error != nil {
   168  				printPutURLsError(&putURLs)
   169  				showLastProgressBar(pg, putURLs.Error.ToGoError())
   170  				return
   171  			}
   172  			urls := doCopy(ctx, doCopyOpts{
   173  				cpURLs:           putURLs,
   174  				pg:               pg,
   175  				encryptionKeys:   encryptionKeys,
   176  				multipartSize:    size,
   177  				multipartThreads: strconv.Itoa(threads),
   178  			})
   179  			if urls.Error != nil {
   180  				e = urls.Error.ToGoError()
   181  				showLastProgressBar(pg, e)
   182  				return
   183  			}
   184  		}
   185  	}
   186  }
   187  
   188  func printPutURLsError(putURLs *URLs) {
   189  	// Print in new line and adjust to top so that we
   190  	// don't print over the ongoing scan bar
   191  	if !globalQuiet && !globalJSON {
   192  		console.Eraseline()
   193  	}
   194  	if strings.Contains(putURLs.Error.ToGoError().Error(),
   195  		" is a folder.") {
   196  		errorIf(putURLs.Error.Trace(),
   197  			"Folder cannot be copied. Please use `...` suffix.")
   198  	} else {
   199  		errorIf(putURLs.Error.Trace(),
   200  			"Unable to upload.")
   201  	}
   202  }
   203  
   204  func showLastProgressBar(pg ProgressReader, e error) {
   205  	if e != nil {
   206  		// We only erase a line if we are displaying a progress bar
   207  		if !globalQuiet && !globalJSON {
   208  			console.Eraseline()
   209  		}
   210  		return
   211  	}
   212  	if progressReader, ok := pg.(*progressBar); ok {
   213  		progressReader.Finish()
   214  	} else {
   215  		if accntReader, ok := pg.(*accounter); ok {
   216  			printMsg(accntReader.Stat())
   217  		}
   218  	}
   219  }