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 }